Skip to content

Is there a way to conditionally include scripts in index.html as in analytics? #4451

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

Closed
sfabriece opened this issue Feb 6, 2017 · 23 comments

Comments

@sfabriece
Copy link

No description provided.

@sfabriece
Copy link
Author

Thanks @Ionaru I'll have a look.

@pdjota
Copy link

pdjota commented Feb 22, 2017

Hi I have the same question. The problem I have is when doing ng build the script code in the index is not included.
I also created this question for the same reason
http://stackoverflow.com/questions/42388641/how-to-include-google-analytics-in-angular-cli-application

Any updates?
Thanks in advance

@Ionaru
Copy link

Ionaru commented Feb 22, 2017

Be sure to put the script code outside the <app-root> tags, because anything inside those tags gets replaced by the Angular application.

@pdjota
Copy link

pdjota commented Feb 22, 2017

thanks @Ionaru including the tag in the HEAD is working for me.

@tobiasheldring
Copy link

The initial question has not really been answered here, I'm also wondering how that could be solved.

Consider if you add f.e. Google Tag Manager or Google Analytics integration to your site, and you have different workspaces (or properties in GA) setup for your different environments. The appropriate script&noscript tags area added to the head and body of index.html but the tracking-id differs between environments.

Best case scenario would of course be if ng build --env=staging/prod would produce two different index.htmls where the tracking id has been set via some dynamic variable.

Until there is support for that, what would be a decent workaround? I guess checking environment variables on server side and serve two different index.html files would be one option. Am using a simple static files server currently though so curious to whether there is some more elegant way of solving this as part of the build process instead

@Ionaru
Copy link

Ionaru commented Mar 8, 2017

You could try adding some code to add different Analytics properties when running in different environments. Like this:

import { environment } from './environments/environment';

if (environment.production) {
  document.write('<script type="text/javascript">// ProductionAnalyticsCodeHere</script>');
} else if (environment.staging) {
  document.write('<script type="text/javascript">// StagingAnalyticsCodeHere</script>');
}

If you do this in main.ts, then the code gets added to the webpage as early as possible.

It is also possible to put document.write() directly into environment.prod.ts, that might be neater.

@filipesilva
Copy link
Contributor

The CLI doesn't provide any such functionality, no. This was requested in #4288.

In past projects what I did was having Angular services that correctly run these scripts, and use the correct analytics token provided inside the environment files.

@Ionaru has provided some pretty good solutions though, cheers!

@mackelito
Copy link

Wont this fail tests? (using document?)

@AndreiShostik
Copy link

@filipesilva I don't think it's a 'pretty good solution', it's mostly a workaround which fortunately works fine

@jfcere
Copy link

jfcere commented Dec 12, 2017

@Ionaru solution is the best workaround I found so far.

main.ts
import { enableProdMode } from '@angular/core';
import { env } from './environments/environment';

if (env.production) {
  // add Google Analytics script to <head>
  const script = document.createElement('script');
  script.innerHTML = `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
  new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
  'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-XXXXXXX');`;
  document.head.appendChild(script);

  // disable Angular development mode
  enableProdMode();
}

There is a "but" to this workaround

Adding the <noscript> section dynamicaly when bootstraping Angular makes no sense as it needs javascript to bootstrap but the tag is to track disabled javascript users.

So be ready to throw away non javascript users tracking if you want to use that workaround OR you could keep the <noscript> section in the body of the index.html file but you'll get tracking for non javascript user from every environments mixed together.

index.html
<body>
  <!-- Google Tag Manager (noscript) -->
  <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
  height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
  <!-- End Google Tag Manager (noscript) -->
  ...
</body>

@edodusi
Copy link

edodusi commented Apr 27, 2018

Solution provided here will not work in Angular Universal, due to direct access to the document object.
You should instead use the DOCUMENT object exported @angular/common. This way I could set a dynamic tagManager id for each environment.

So in the AppComponent:

import { DOCUMENT } from '@angular/common';
...
  public constructor(
    @Inject(DOCUMENT) private doc: any
  ) {

Then I used a private method called on ngOnInit() to insert the tag in <head>:

  private setGTagManager() {
    const s = this.doc.createElement('script');
    s.type = 'text/javascript';
    s.innerHTML = '(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push'
      + '({\'gtm.start\':new Date().getTime(),event:\'gtm.js\'});var f=d.getElementsByTagName(s)'
      + '[0],j=d.createElement(s),dl=l!=\'dataLayer\'?\'&l=\'+l:\'\';j.async=true;j.src='
      + '\'https://www.googletagmanager.com/gtm.js?id=\'+i+dl;f.parentNode.insertBefore(j,f);})'
      + '(window,document,\'script\',\'dataLayer\',\'' + environment.tagManagerId + '\');';
    const head = this.doc.getElementsByTagName('head')[0];
    head.appendChild(s);
  }

As you can see I imported the tagManager ID from the global environment, so I could use different ones for dev and production.

Also if you use Angular Universal be sure to run this code only on the server, or it will import the script twice in the browser.

Hope this helps.

@ghost
Copy link

ghost commented Jun 8, 2018

For analytics you can use multiple index.html pages (like index.prod.html, etc) and fileReplacements (https://github.com/angular/angular-cli/wiki/stories-application-environments).
It works for html files starting from Angular CLI 6.1.0-beta.2.

@ramk-bharathi
Copy link

@pbazurin-softheme how to change between different index.html files with configurations, any example or snippets? I tried with changing "options" but it didnt worked.

@ghost
Copy link

ghost commented Jun 25, 2018

{
...
         "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                },
                {
                  "replace": "src/index.html",
                  "with": "src/index.prod.html"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true
            },
            "staging": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.staging.ts"
                },
                {
                  "replace": "src/index.html",
                  "with": "src/index.staging.html"
                }
              ],
...
}

@ramk-bharathi This is my angular.json.
You can add as many additional build configurations as you want

@pcwa-ahendricks
Copy link

@pbazurin-softheme it seems like your snippet would work for my needs (adding tags instead of <script> tags) but it doesn't. Are you certain that it works for you? If not, maybe the index.html in /src is handled differently in Angular CLI vs other files. Currently I have to use pre and post hooks with my npm package scripts in order to swap out the correct index.html.

@meetai
Copy link

meetai commented Jun 26, 2018

Seemingly that example from @pbazurin-softheme would work starting from Angular CLI 6.1.0-beta.2, as explained above.

Check the releases.

@wheredoesyourmindgo
Copy link

@meetai ahh. Yes. My comment was specific to cli 6.0.8. Apprciate the clarification.

@anywhichway
Copy link

This client side package should also address the original question outside the context of the Angular CLI: https://github.com/anywhichway/scriptswitch.

@boogiebit
Copy link

@lonaru solution worked for me also. Thanks!

@nicky-lenaers
Copy link

@pbazurin-softheme Your solution is the most stable one. It prevents adding script tags at runtime (which would nullify the <noscript> tag usage). It's a little bit of maintenance overhead, but in my opinion it prevents possibly hard-to-debug errors when injecting script tags dynamically. Thanks!

@toby5box
Copy link

toby5box commented Jul 2, 2019

The CLI doesn't provide any such functionality, no. This was requested in #4288.

In past projects what I did was having Angular services that correctly run these scripts, and use the correct analytics token provided inside the environment files.

@Ionaru has provided some pretty good solutions though, cheers!

Please edit this to point to the much better answer at #4451 (comment)

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