mzuccaroli / angular-google-tag-manager

A service library for integrate google tag manager in your angular project
https://itnext.io/how-to-add-google-tag-manager-to-an-angular-application-fc68624386e2
MIT License
136 stars 78 forks source link

Get the GTM ID from external (lazyload angular-google-tag-manager module) #58

Open monsieurPaon opened 3 years ago

monsieurPaon commented 3 years ago

Hi !

I'm working on projects who need to setup different GTM ID in each environnement. The variable comme from an API call and cannot be setup in the env files.

I have try to do, something like that:

`import {map} from 'rxjs/operators'; import {API_PUBLIC} from '../../../environments/environment'; import {HttpClient} from '@angular/common/http'; import {GoogleTagManagerConfig} from 'angular-google-tag-manager';

export function googleTagManagerConfigFactory(http: HttpClient, gtConf: GoogleTagManagerConfig) { return () => http.get<{ gtmTag: string }>(API_PUBLIC + '/config').pipe( map(response => { if (gtConf) { gtConf.id = response.gtmTag; return response.gtmTag; } }) ).toPromise(); }`

providers: [ {provide: GoogleTagManagerConf, useFactory: googleTagManagerConfigFactory} ]

But i'm facing with this error => ERROR in ./src/app/app.module.ts 123:31-51 "export 'GoogleTagManagerConf' was not found in 'angular-google-tag-manager'

Do you have a solution to lazy load the angularTagManagerModule ?

ancifer commented 3 years ago

use

{ provide: 'googleTagManagerId', useFactory: googleTagManagerFactory, deps: [AppConfig] }, export function googleTagManagerFactory(config: AppConfig) { return config.analytics.googleTagManagerId; }

monsieurPaon commented 3 years ago

What do you meen with 'AppConfig' ?

ancifer commented 3 years ago

What do you meen with 'AppConfig' ?

AppConfig - you app configuration with googleTagManagerId For example:

AppModule -> providers
{
      provide: APP_INITIALIZER,
      useFactory: AppConfigurationFactory,
      deps: [AppConfig],
      multi: true,
}

export function AppConfigurationFactory(config: AppConfig) {
  return () => config.init();
}

@Injectable({
  providedIn: 'root',
})
export class AppConfig {
  googleTagManagerId: string;
  constructor(private httpClient: HttpClient) {}

  ensureInit(): Promise<AppConfig> {
    return new Promise((r) => {
      this.httpClient
        .get('/config.json', { responseType: 'text' })
        .toPromise()
        .then((res) => {
          const data = JSON.parse(res);

          this.googleTagManagerId = data.googleTagManagerId; 
          r(this);
        });
    });
  }
}
monsieurPaon commented 3 years ago

Ho dorry okey i understand. I have try this before but i don't understand how you link the return of the function to the tag manager module. I'm already doing something similar with an other module but in deps of the provider i can call the conf injectionToken.

Error: Google tag manager ID not provided.

monsieurPaon commented 3 years ago

    {
      provide: APP_INITIALIZER,
      useFactory: agmConfigFactory,
      deps: [HttpClient, LAZY_MAPS_API_CONFIG],
      multi: true
    },
    {
      provide: APP_INITIALIZER,
      useFactory: AppConfigurationFactory,
      deps: [HttpClient, AppConfig],
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: RequestInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

export function AppConfigurationFactory(config: AppConfig): any {
  return () => config.ensureInit();
}

export function HttpLoaderFactory(http: HttpClient): any {
  return new TranslateHttpLoader(http, '../assets/locales/', '.json');
}```
tyanders1987 commented 3 years ago

Is there a way to load this module or pass it the GTM Id after the app is fully initialized?

monsieurPaon commented 3 years ago

I think the @ancifer response is the right way, but i still lock on my error.

ancifer commented 3 years ago

@monsieurPaon All code. App config init run APP_INITIALIZER. After run googleTagManagerId. I hope I helped.

@Injectable({
  providedIn: 'root',
})
export class AppConfig {
  googleTagManagerId: string;
  constructor(private httpClient: HttpClient) {}

  init(): Promise<AppConfig> {
    return new Promise((r) => {
      this.httpClient
        .get<AppConfig>('/assets/config.json')
        .toPromise()
        .then((response: AppConfig) => {
          Object.assign(this, response);
          r(this);
        });
    });
  }
}

@NgModule({
  declarations: [AppComponent],
  imports: [
    GoogleTagManagerModule
  ],
  providers: [
    AppConfig,
    {
      provide: APP_INITIALIZER,
      useFactory: AppConfigurationFactory,
      deps: [AppConfig],
      multi: true,
    },
    { provide: 'googleTagManagerId', useFactory: GoogleTagManagerId, deps: [AppConfig] },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

export function AppConfigurationFactory(appConfig: AppConfig) {
  return () => appConfig.init();
}

function GoogleTagManagerId(appConfig: AppConfig) {
  return appConfig.googleTagManagerId;
}

config.json in assets

{
    googleTagManagerId: "YOU GOOGLE TAG MANAGER ID"   
}
oajulianclementson commented 11 months ago

I've also tried the approach from @ancifer but it doesn't work for me. The googleTagManagerId factory completes before the APP_INITIALIZER promise has resolved, because of the time taken to fetch config.json.

Although Angular waits for APP_INITIALIZER promises to resolve before loading components, it doesn't wait before loading other providers, so they are loaded in parallel.

So I think that rules out using this library with a config that is loaded using APP_INITIALIZER. The only solution would be to load the config in main.ts, before boostrapping the application.