Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<html> component #533

Closed
PatrickJS opened this issue Sep 16, 2016 · 32 comments
Closed

<html> component #533

PatrickJS opened this issue Sep 16, 2016 · 32 comments

Comments

@PatrickJS
Copy link
Member

PatrickJS commented Sep 16, 2016

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>
@FrozenPandaz
Copy link
Contributor

Scripts aren't rendering again. That will be a blocker for this.

@cipriansorlea
Copy link

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.

@jeffwhelpley
Copy link
Contributor

@jeffbcross this is a super high priority issue that we need to talk about this week

@FrozenPandaz
Copy link
Contributor

UNIVERSAL_CACHE doesn't make it onto the page when using a HTML Component after 2.1.0-rc.1

@PatrickJS
Copy link
Member Author

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

@FrozenPandaz
Copy link
Contributor

FrozenPandaz commented Oct 14, 2016

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:

  • Title Service (Angular already has one, we just need to make it universal?)
  • Meta Service should add custom metas
  • Assets Service should add script and css tags
    • This should allow for inline tags as well
    • EDIT: This should handle onloads of server created assets for client side code (Universal onload)
  • Doctype Service should edit the kind of html it is. AMP for example requires a <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:

  1. These Services are provided to the app. Starts off with what the index.html contains. This should allow the implementer to very naturally transition from a client only Ng2 app.
  2. During the execution of the app, it will just keep a state of what should be on the page.
  3. Then finally onStable the component should pull from these services to generate the final document.

I have no idea how practical that sounds but hopefully it helps.

@u12206050
Copy link

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.

@jeffwhelpley
Copy link
Contributor

@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.

@zcoon
Copy link

zcoon commented Oct 22, 2016

Hi @jeffwhelpley do you have any updated info for us after your meeting yesterday?

@jeffwhelpley
Copy link
Contributor

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.

@kuldeepkeshwar
Copy link

@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.

@kuldeepkeshwar
Copy link

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;
            }
        }
    }
}

@u12206050
Copy link

u12206050 commented Oct 24, 2016

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;
          }
      }
  }
}

@jeffbcross
Copy link
Contributor

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: angular/angular#12322 Please add comments there if you have a meta tag use case that wouldn't be covered by that service.

@zcoon
Copy link

zcoon commented Oct 25, 2016

@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 : #534 . Consequently I am trying to find the best way to make my web app SEO friendly and richly sharable to FB/Twitter asap.

@PatrickJS
Copy link
Member Author

PatrickJS commented Oct 25, 2016

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.

@pxwise
Copy link

pxwise commented Oct 26, 2016

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.

@jeffwhelpley
Copy link
Contributor

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.

@pxwise
Copy link

pxwise commented Oct 26, 2016

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.

@jeffwhelpley
Copy link
Contributor

@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?

@pxwise
Copy link

pxwise commented Oct 26, 2016

@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>

  • rel="canonical"
  • rel="dns-prefetch", based on underlying api, 3rd party site dependencies, per page
  • css, needed as stopgap while we migrate legacy design to 100% angular embedded styles

<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.

@jeffwhelpley
Copy link
Contributor

Got it. OK, we are talking about the MetaService with @jeffbcross and I will be sure to include all of these in the discussion.

@jeffbcross
Copy link
Contributor

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.

@jeffbcross
Copy link
Contributor

Sorry, my last comment ignored some of the intermediary comments, I thought we were still talking about the context of the HTML Component.

@pxwise
Copy link

pxwise commented Oct 28, 2016

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 authorization

The 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.

@PatrickJS
Copy link
Member Author

@pxwise any feedback from trying out UniversalAd?

@pxwise
Copy link

pxwise commented Oct 31, 2016

@gdi2290 yes I got it working, thanks for the gist. I'll ping you later today to review what I have, made some changes.

@u12206050
Copy link

@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.

@yazeedb
Copy link
Contributor

yazeedb commented Nov 3, 2016

@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?

@jrmcdona
Copy link

jrmcdona commented Aug 2, 2017

@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?

@PatrickJS
Copy link
Member Author

you can mix them, yeah

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests