document and write about maintaining an html component as the document
html.module.ts
@NgModule({
bootstrap: [ HtmlComponent ],
declarations: [ HtmlComponent ],
imports: [
UniversalModule,
FormsModule
]
})
export class MainModule {}
html.template.ts
<html>
<head>
<title></title>
</head>
<body>
<app></app>
</body>
</html>
index.html
<!DOCTYPE html><html></html>
Scripts aren't rendering again. That will be a blocker for this.
This prevents any website built with Universal to be SEO friendly and have custom titles and meta tags. Any clue when this would be available? In RC4 I was able to get Universal running and be SEO friendly, but with the GA version, that seems to no longer work.
@jeffbcross this is a super high priority issue that we need to talk about this week
UNIVERSAL_CACHE doesn't make it onto the page when using a HTML Component after 2.1.0-rc.1
the problem here is that we have a DOM created from index.html and a new one created via the <html> component. When attaching our script to the dom we were previously adding it to the document itself which places it after the <html> tag. Now we're adding it in the body tag but we're referencing the old body tag rather than the one created by the <html> component
I feel like we should generate the document with a HTML Component of sorts.
The document should start off with the implementer's index.html but then be compiled by hooking into services provided to the implementer.
It should work off of a variety of services:
<html âš¡> tag instead of the traditional <html>.In the browser, these services should do their javascript equivalents.
In execution, if we could find away to wrap around the user's app it would just be a component containing the implementer's app and this should be rather straightforward.
Otherwise it might be something like:
I have no idea how practical that sounds but hopefully it helps.
Any updates on this "high important priority"? We have a production site ready for launch and the only thing we are missing is SEO, (being able to render meta tags server side).
We have a working meta service that does it correctly on the browser side, (obviously useless though) and I have experimented with all the various renders and getDom methods I could find in angular, but like @gdi2290 mentioned the references I get seem to not be the same one ending up being rendered.
Any help will be greatly appreciated.
@u12206050 Sorry about the delay in updates on this. We are meeting to discuss status of all high priority items tomorrow and I will provide some info on this and all other issues after that.
Hi @jeffwhelpley do you have any updated info for us after your meeting yesterday?
The solution for this issue is going to be a new SeoService similar to setTitle that will allow you to make any of the modifications you want to the head section of the document. @gdi2290 will likely get an additional prototype into the universal starter to get everyone started and then we will get something into universal and/or core (TBD) later on. So, watch the starter repo.
@gdi2290 @jeffwhelpley can you share any gist which we can use as temparory solution as most of us are very close to the production release.
as of now I'm using below solution
import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/platform-browser';
import { SeoMetaData } from "./../../model/seo-meta-data";
@Injectable()
export class SEOService {
constructor( @Inject(DOCUMENT) private document: any) {}
public setData(metaData: SeoMetaData): void {
this.setMeta(metaData.title,metaData.description);
}
private setMeta(title: string = '', description: string = '') {
this.setTitle(title);
this.setMetaDescription(description);
}
private setTitle(title: string) {
this.document.title = title;
}
private setMetaDescription(description: string) {
let headChildren = this.document.head.children;
for (let i = 0; i < headChildren.length; i++) {
let element = headChildren[i];
if(element.name === 'meta' && element.attribs.name === 'description'){
element.attribs.content = description;
}
}
}
}
For any one else wanting a full service to set meta tags incl social:
Include the following in the head of index.html
<meta name="title" content="">
<meta name="og:title" content="">
<meta name="description" content="">
<meta name="og:description" content="">
<meta name="keywords" content="">
<meta name="og:url" content="">
<meta name="og:image" content="">
<meta name="og:site_name" content="">
<meta name="article:published_time" content="">
<meta name="article:modified_time" content="">
<meta name="article:tag" content="">
<meta name="twitter:card" content="">
<meta name="twitter:site" content="">
<meta name="twitter:creator" content="">
<meta name="og:type" content="">
<meta name="fb:app_id" content="">
and here is the entire service, you can call updateMetaTags(meta: object) with an object of key (meta name), value (meta content) pairs:
import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/platform-browser';
declare var global: any;
@Injectable()
export class SEOMetaService {
metaTags: any[];
constructor(@Inject(DOCUMENT) private document: any) {
this.metaTags = this.document.head.children;
}
public updateMetaTags(meta: any = {}) {
if (!this.document) return null;
if (meta.disableUpdate) {
return false;
}
try {
this.setTitle(meta.title, meta.titleSuffix);
Object.keys(meta).forEach(key => {
if (key === 'title' || key === 'titleSuffix')
return;
this.setMetaTag(key, meta[key]);
if (key == 'description')
this.setMetaTag('og:description', meta[key]);
});
} catch(err) {
console.log(err);
}
}
private setTitle(title?: string, titleSuffix?: string) {
let titleStr = title || '';
if (titleStr.indexOf(titleSuffix) < 0)
titleStr += (!!titleSuffix ? ' | '+titleSuffix : '');
this.setMetaTag('title',titleStr);
this.setMetaTag('og:title',titleStr);
this.document.title = titleStr;
}
private setMetaTag(name: string, value: string) {
let len = this.metaTags.length;
for (let i = 0; i < len; i++) {
let element = this.metaTags[i];
if(element.name === 'meta' && element.attribs.name === name){
element.attribs.content = value;
break;
}
}
}
}
After some discussion, we decided not to support the HTML Component feature as it was previously implemented. We'll be working on making sure the Title service works in Universal, and will add a similar Meta service to manage meta tags. There's already a good start on the Meta service for platform-browser, which would be ported to Universal: https://github.com/angular/angular/pull/12322 Please add comments there if you have a meta tag use case that wouldn't be covered by that service.
@u12206050 using that SeoService, are you successfully rendering the meta-tags server side so that sites like FB can pick up on proper og tags to load the content richly within Facebook?
I also have a web app blocked from production due to this issue and the lack of access to window properties documented here : https://github.com/angular/universal/issues/534 . Consequently I am trying to find the best way to make my web app SEO friendly and richly sharable to FB/Twitter asap.
closing as <html> not supported by angular anymore.
For Seo concerns comments on Meta Service
For everything else use EJS or Jade as known as Pug.
For Ads see UniversalAd (client side only) prototype if it works for you
For "flickering" issues use Cache service Universal Cache
For removing <universal-script> see universalAfterDehydrate
For selected attribute workaround see UniversalSelected
HTML comment is great because it reverts the responsibility of these global services to the developer rather than the framework. It was also great because you can use Angular all the way down which was a big driver for developers using Universal. The problem here is that it's now impossible without more changes to core. There was a time where html component worked which was before a design change in core before final. A lot of these services are global services that we can call platform-services. For examples, services/directives such as Meta, Scripts, Cookies, Base, and Host Styles are all platform concerns that were never really correctly addressed until the final release since the focus was on the core api.
If an <html> component is out of scope for core and universal is there happy path to making this feature a community contributed universal module? Our peers on another team are using react-helmet to interface with dynamic CMS generated head data including <title>, <meta>, <link> styles, <link> dns-prefetch, <link> apple-touch-icon, <script> json-ld, <script> 3rd party ads src, <html> lang and AMP âš¡ attributes. Could a universal analog to react-helmet be created in a non-hacky way?
I'll test out UniversalAd with real world ads and edge compute transformed markup. In the meantime I'll investigate EJS for generating a dynamic document.
To be really clear here, you most definitely will be able to have control over tags in the head section. The change in strategy is that before we were trying to treat the entire html document as a component. This would be cool in theory and allow you to define your entire document in the same way as you do other components, but it is just not feasible to implement that at this time. Instead, we are going to focus on providing access to the head section through services. So, it should be as simple as the setTitle service. The implication then is that you would be modifying the head programmatically (usually in a service that listens for route changes) instead of declaratively in a template.
So, the capability that is needed will be there. Just with a different approach.
I see, so in order to have feature parity with react-helmet we would need services for <script> <link> and <html> attributes in addition to the <title> and <meta> services.
I agree it would be cool to declaratively build the document as a component. Easy to visualize, all markup generated through the same template engine. Maybe we can lobby to reopen this feature request for a future major version.
@pxwise so, for the html element, do you foresee needing to change anything other than the lang for i18n sites?
What would be the use case for the link and script tags? I would think those would be static from one request to another, no?
@jeffwhelpley for <html> We will only need lang and âš¡
Yes many link and script tags would be static but some use cases for dynamic would be:
<link>
<script>
I am looking at this need from the initial server generated page load, not on route change which is more pertinent to title and meta. Ideally we have one html to maintain for the entire site and configure upstream at the CMS. Otherwise we'd require new universal deploys when we add / edit / delete pages with varying link and script tags.
Got it. OK, we are talking about the MetaService with @jeffbcross and I will be sure to include all of these in the discussion.
Hey @pxwise thanks for more use cases. I'd like to determine how these cases depend on dynamic _data_ within Angular, to understand better if there should be support within Angular. In other words, why must Angular solve this problem instead of a template engine or third-party library? I'd assume a lot of those use cases just need to know the URL of the page in order to know what values to populate those link and script tags with.
And just to give background on why I'm pushing back so much on this feature; I do see the convenience of having first-class support for html documents. It requires thoughtful design and security review to implement and maintain such functionality, since Angular was not designed to support this.
Sorry, my last comment ignored some of the intermediary comments, I thought we were still talking about the context of the HTML Component.
Howdy @jeffbcross. Some cases above that could use data from angular components would be:
<link> dns-prefetch to automatically prefetch domains used by components making api requests.<script> JSON-LD schema.org definitions for a recipe site on a dynamically fetched route /recipe/node/123<script> include / exclude ad script based on user authorizationThe other tags could be populated with a separate config object, mapping url path -> page state values, driven by a node template engine.
I still think an HTML Component would be a nice to have allowing for a simple mental model to construct dynamic pages but understand how this introduces significant expansion of scope for angular. I guess it's a matter of determining angular's boundaries as a server side templating language.
The intent of my initial response was requesting guidance for creating a 3rd party module to serve as an angular HTML component or service as a supplement to angular universal, to avoid the need for a node templating engine if this feature were not available in core.
@pxwise any feedback from trying out UniversalAd?
@gdi2290 yes I got it working, thanks for the gist. I'll ping you later today to review what I have, made some changes.
@zcoon sorry for the late reply, but yes it seems to work great. We have some extra checks happening somewhere else making sure we attach the correct image to use on social websites though.
@u12206050 This solution looks great! Is there a way to dynamically create the meta and inject it into the document head server-side? I've tried the following:
let meta_tag = getDOM().createElement('meta');
DOM.setAttribute(meta_tag, 'name', meta.name);
getDOM().appendChild(this.document.head, meta_tag);
But it seems to break my client-side render if I tamper with the document. Any ideas why?
@gdi2290 is it possible to use the ejs engine with angular? All I need ejs for is for the layout (header) because I do have custom script and link tags because mine are dynamic. This very easily solved with templating but when mixing it in with Angular I don't see how it would work given I only want to use it for the layout.
@pxwise how did you solve for dynamic link and script tags?
you can mix them, yeah
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._
Most helpful comment
For any one else wanting a full service to set meta tags incl social:
Include the following in the head of index.html
and here is the entire service, you can call
updateMetaTags(meta: object)with an object of key (meta name), value (meta content) pairs: