EmmanuelRoux / ngx-matomo-client

Matomo analytics client for Angular applications
https://matomo.org/
MIT License
74 stars 16 forks source link

Directives in templates aren't working whereas all the rest is. #69

Closed natalie-o-perret closed 1 year ago

natalie-o-perret commented 1 year ago

Hello

Thanks for your hard work! Love that your package provides some capabilities for async initialization.

I've set up Matomo and it works to some extent. I have some application in Angular using PrimeNG and I get the routing events, as well as the customized bits I'm passing (SSO user) with the interceptor. So far, so good, except when I'm using the directives in component templates like described in https://github.com/EmmanuelRoux/ngx-matomo#tracking-events, e.g.,

<button
  type="button"
  matomoClickCategory="myCategory"
  matomoClickAction="myAction"
  matomoClickName="myName"
  [matomoClickValue]="42"
>
  Example for tracking button clicks
</button>

=>

Can't bind to 'matomoClickValue' since it isn't a known property of 'button'.ngtsc(-998002)
whatever.component.ts(17, 4): Error occurs in the template of component WhateverComponent.

I'm wondering if you have some hints about why this would be the case?


When using for instance something like, the other approach, event-based:

*.ts code:

// ...
@Component({
})
export class WhateverComponent {
  constructor(private readonly _matomoTracker: MatomoTracker)
// ...
  public onClick() {
    this._matomoTracker.trackEvent("Export", action);
  }
// ...
}

*.html template code:

<button (click)="onClick()">  Example for tracking button clicks</button>

It works.


Notes:

EmmanuelRoux commented 1 year ago

Hi @natalie-o-perret

Could you share your configuration please ? So I can take a deeper look

If you are using NgxMatomoModule.forRoot(...), it should work as-is.

If you are using provideMatomo(...), then you must either import each directive individually or import MATOMO_DIRECTIVES (to import them all at once)

natalie-o-perret commented 1 year ago

Hi @natalie-o-perret

Could you share your configuration please ? So I can take a deeper look

If you are using NgxMatomoModule.forRoot(...), it should work as-is.

If you are using provideMatomo(...), then you must either import each directive individually or import MATOMO_DIRECTIVES (to import them all at once)

Hey there @EmmanuelRoux, thanks your comment,

So I created the module below:

import { NgModule } from "@angular/core";
import { NgxMatomoModule, MatomoInitializationMode, NgxMatomoRouterModule } from "ngx-matomo-client";
import { MatomoSsoUserRouterInterceptor } from "./interceptors/matomo-sso-user-router.interceptor";

@NgModule({
    declarations: [
    ],
    imports: [
      NgxMatomoModule.forRoot({
          mode: MatomoInitializationMode.AUTO_DEFERRED,
        }),
        NgxMatomoRouterModule.forRoot({
          interceptors: [
            MatomoSsoUserRouterInterceptor,
          ]
        }),
    ],
  })
export class MatomoModule {}

as well as this service:

import { Injectable } from "@angular/core";
import { MatomoInitializerService } from "ngx-matomo-client";
import { ConfigurationService } from "../../config/services/configuration.service";

@Injectable()
export class MatomoInitializationService {

  constructor(
      private readonly _matomoInitializerService: MatomoInitializerService,
      private readonly _configurationService: ConfigurationService) 
      {}

  public init() {
    this._matomoInitializerService.initializeTracker({
      trackerUrl: this._configurationService.config.matomoTrackerUrl,
      scriptUrl: this._configurationService.config.matomoScriptUrl,
      siteId: this._configurationService.config.matomoId,
      })
  }
}

and the interceptor I've mentioned before:

import { Injectable } from '@angular/core';
import { NavigationEnd } from '@angular/router';
import { MatomoRouterInterceptor, MatomoTracker } from 'ngx-matomo-client';
import { AuthenticationService } from '../../auth/services/authentication.service';

@Injectable()
export class MatomoSsoUserRouterInterceptor implements MatomoRouterInterceptor {
constructor(
    private readonly _authenticationService: AuthenticationService,
    private readonly _matomoTracker: MatomoTracker
) {}

  beforePageTrack(_: NavigationEnd): void {
    if (this._authenticationService.isAuthenticated()) {
        const identityClaims = this._authenticationService.getIdentityClaims();
        if (identityClaims) {
            this._matomoTracker.setUserId(identityClaims.email);
        }
    }
  }
}

in my app.module.ts, I roughly have something like that:

export const appInitFactory =
  (
    injector: Injector,
    configurationService: ConfigurationService,
    authenticationService: AuthenticationService,
    translateService: TranslateService,
    translateStorageService: TranslateStorageService,
    matomoInitializationService: MatomoInitializationService
  ) =>
    async (): Promise<void> => {
      await configurationService['load']();
      await authenticationService['load']();
      await injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
      translateService.setDefaultLang('en');
      translateService.addLangs(['fr', 'es']);
      await firstValueFrom(
        translateStorageService.getStoreAndUseOrDefaultBrowserLang(
          translateService.defaultLang as Language
        )
      );
      matomoInitializationService.init();
      return;
    };

function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

@NgModule({
  declarations: [AppComponent, HomePageComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    TranslateModule.forRoot({
      defaultLanguage: 'en',
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient],
      },
      missingTranslationHandler: {
        provide: MissingTranslationHandler,
        useClass: MissingTranslationHandlerDefault,
      },
    }),
    AppRoutingModule,
    OAuthModule.forRoot(),
    MatomoModule,
    CoreModule,
    AppLayoutModule,
    ToastModule,
    ButtonModule,
    LayoutModule,
  ],
  providers: [
    ConfigurationService,
    MatomoInitializationService,
    AuthenticationService,
    {
      provide: APP_INITIALIZER,
      useFactory: appInitFactory,
      deps: [
        Injector,
        ConfigurationService,
        AuthenticationService,
        TranslateService,
        TranslateStorageService,
        MatomoInitializationService,
      ],
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthBearerTokenInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptorService,
      multi: true,
    },
    {
      // Processes all errors
      provide: ErrorHandler,
      useClass: GlobalErrorHandlerService,
    },
    MessageService,
  ],
  bootstrap: [AppComponent],
})
export class AppModule { }
EmmanuelRoux commented 1 year ago

Try adding exports: [NgxMatomoModule] to your MatomoModule.

This is related to how NgModules work in Angular (see https://angular.io/guide/ngmodules)

natalie-o-perret commented 1 year ago

I already tried that before for the same reason but I'm getting the same exact error.

EmmanuelRoux commented 1 year ago

Can you share a reproduction of the error ? (Stackblitz, repo...)

natalie-o-perret commented 1 year ago

Can you share a reproduction of the error ? (Stackblitz, repo...)

I just managed to get it work, my bad, there was another export missing πŸ€¦β€β™€οΈ. Tbs, not sure to fully understand the behaviour, because my MatomoModule was already part of the AppModule

    AppRoutingModule,
    OAuthModule.forRoot(),
    MatomoModule,
    CoreModule,
    AppLayoutModule,
    ToastModule,
    ButtonModule,
    LayoutModule,

Even tried different orderings, so ended up doing adding my MatomoModule straight to the CoreModule exports and there it started to work, not sure why it doesn't flow accordingly without itπŸ€”. I guess my assumptions were wrong πŸ˜”

[EDIT 1]

The problem doing so, is that there are now multiple initializations. πŸ€”

I see why now, hmmmph, that's a bit of a bugger to architect that one properly πŸ€”


[EDIT 2]

Gotcha, so I wanted to wrap everything in my MatomoModule including the init part which was called again and again upon import in another module, ended up discarding that wrapping MatomoModule and flatten the module structure with the NgxMatomoModule directly:

@NgModule({
  declarations: [AppComponent, HomePageComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    TranslateModule.forRoot({
      defaultLanguage: 'en',
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient],
      },
      missingTranslationHandler: {
        provide: MissingTranslationHandler,
        useClass: MissingTranslationHandlerDefault,
      },
    }),
    AppRoutingModule,
    OAuthModule.forRoot(),
    CoreModule,
    AppLayoutModule,
    ToastModule,
    ButtonModule,
    LayoutModule,
    NgxMatomoModule.forRoot({
      mode: MatomoInitializationMode.AUTO_DEFERRED,
    }),
    NgxMatomoRouterModule.forRoot({
      interceptors: [
        MatomoSsoUserRouterInterceptor,
      ]
    })
  ],
  providers: [
    ConfigurationService,
    MatomoInitializationService,
    AuthenticationService,
    {
      provide: APP_INITIALIZER,
      useFactory: appInitFactory,
      deps: [
        Injector,
        ConfigurationService,
        AuthenticationService,
        TranslateService,
        TranslateStorageService,
        MatomoInitializationService,
      ],
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthBearerTokenInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptorService,
      multi: true,
    },
    {
      // Processes all errors
      provide: ErrorHandler,
      useClass: GlobalErrorHandlerService,
    },
    MessageService,
  ],
  bootstrap: [AppComponent],
  exports: []
})
export class AppModule { }

and in my core module:

  exports: [
    //...
    NgxMatomoModule
  ],

Sorry about all that noise, and thanks for the pointers! πŸ™‡β€β™€οΈ