Aller au contenu principal
11 min de lecture

Exemple d'intégration Angular

Ce guide fournit un exemple complet et prêt pour la production d'intégration des analyses Zenovay dans une application Angular à l'aide des modèles Angular modernes, des services et de l'injection de dépendances.

Démarrage rapide

Ajoutez le script de suivi Zenovay à votre index.html :

<!-- src/index.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My Angular App</title>
</head>
<body>
  <app-root></app-root>
  <script defer data-tracking-code="YOUR_TRACKING_CODE" src="https://api.zenovay.com/z.js"></script>
</body>
</html>

C'est tout ce qui est nécessaire pour le suivi basique des pages vues. Le script suit automatiquement les pages vues lors du chargement.

Carte de script de suivi montrant la balise de script avec les onglets HTML, React, Next.js et First-Party ainsi qu'un bouton de vérification d'installation
Trouvez votre code de suivi sous Domaines → votre site → Général.

Configuration de base

1. Créer un service d'analyse

Créez un service pour envelopper l'objet global zenovay avec l'injection de dépendances Angular :

// services/analytics.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AnalyticsService {
  private get zenovay(): any {
    return (window as any).zenovay;
  }

  track(eventName: string, data?: Record<string, any>): void {
    if (this.zenovay) {
      this.zenovay('track', eventName, data);
    }
  }

  identify(userId: string, traits?: Record<string, any>): void {
    if (this.zenovay) {
      this.zenovay('identify', userId, traits);
    }
  }

  trackGoal(goalName: string, data?: Record<string, any>): void {
    if (this.zenovay) {
      this.zenovay('goal', goalName, data);
    }
  }

  trackPurchase(data: { amount: number; currency: string; product: string }): void {
    if (this.zenovay) {
      this.zenovay('revenue', data.amount, data.currency);
    }
  }
}

2. Suivre les pages vues (Angular Router)

Créez un service pour suivre les changements de route :

// services/page-tracking.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { AnalyticsService } from './analytics.service';
import { filter } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class PageTrackingService {
  constructor(
    private router: Router,
    private analytics: AnalyticsService
  ) {
    this.initPageTracking();
  }

  private initPageTracking(): void {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe((event: NavigationEnd) => {
      this.analytics.track('pageview', {
        path: event.urlAfterRedirects,
        referrer: document.referrer,
        title: document.title,
      });
    });
  }
}

Initialisez dans votre composant d'application :

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { PageTrackingService } from './services/page-tracking.service';

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>',
})
export class AppComponent implements OnInit {
  constructor(private pageTracking: PageTrackingService) {}

  ngOnInit(): void {
    // Service automatically starts tracking on init
  }
}

3. Suivre les événements personnalisés

Utilisez le service d'analyse dans vos composants :

// components/signup-button/signup-button.component.ts
import { Component } from '@angular/core';
import { AnalyticsService } from '../../services/analytics.service';

@Component({
  selector: 'app-signup-button',
  template: `
    <button (click)="handleSignup()">
      Start Free Trial
    </button>
  `
})
export class SignupButtonComponent {
  constructor(private analytics: AnalyticsService) {}

  handleSignup(): void {
    this.analytics.track('signup_started', {
      plan: 'professional',
      source: 'pricing_page'
    });

    // Continue with signup logic
  }
}

Exemple complet

Voici une implémentation complète avec tous les cas d'usage courants :

Service d'analyse avec configuration

// services/zenovay-analytics.service.ts
import { Injectable } from '@angular/core';
import { environment } from '../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class ZenovayAnalyticsService {
  private get zenovay(): any {
    return (window as any).zenovay;
  }

  // Page tracking
  trackPageView(path: string, title?: string): void {
    if (this.zenovay) {
      this.zenovay('track', 'pageview', {
        path,
        title: title || document.title,
        referrer: document.referrer,
      });
    }
  }

  // Custom events
  trackEvent(eventName: string, data?: Record<string, any>): void {
    if (this.zenovay) {
      this.zenovay('track', eventName, {
        ...data,
        timestamp: new Date().toISOString(),
        environment: environment.production ? 'production' : 'development',
      });
    }
  }

  // User identification
  identifyUser(userId: string, traits?: Record<string, any>): void {
    if (this.zenovay) {
      this.zenovay('identify', userId, traits);
    }
  }

  // Debug mode
  enableDebug(): void {
    if (!environment.production) {
      console.log('[Zenovay] Debug mode enabled');
    }
  }
}

Directive de bouton traçable

// directives/trackable-button.directive.ts
import { Directive, HostListener, Input } from '@angular/core';
import { AnalyticsService } from '../services/analytics.service';

@Directive({
  selector: '[appTrackableButton]',
  standalone: true
})
export class TrackableButtonDirective {
  @Input() eventName!: string;
  @Input() eventData?: Record<string, any>;

  constructor(private analytics: AnalyticsService) {}

  @HostListener('click', ['$event'])
  onClick(event: MouseEvent): void {
    if (!this.eventName) {
      console.warn('[TrackableButton] No event name provided');
      return;
    }

    this.analytics.track(this.eventName, {
      ...this.eventData,
      button_text: (event.target as HTMLElement).textContent,
      timestamp: Date.now(),
    });
  }
}

Exemple d'utilisation

// pages/pricing/pricing.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TrackableButtonDirective } from '../../directives/trackable-button.directive';

@Component({
  selector: 'app-pricing',
  standalone: true,
  imports: [CommonModule, TrackableButtonDirective],
  template: `
    <div class="pricing-page">
      <h1>Pricing</h1>

      <button
        appTrackableButton
        [eventName]="'plan_selected'"
        [eventData]="{
          plan: 'scale',
          billing: 'monthly',
          price: 90
        }"
        class="btn-primary">
        Choose Scale
      </button>
    </div>
  `
})
export class PricingComponent {}

Modèles avancés

Suivi conditionnel

Suivez les événements uniquement lorsque certaines conditions sont remplies :

// pages/product/product.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { AnalyticsService } from '../../services/analytics.service';

interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
}

@Component({
  selector: 'app-product',
  template: `<div>{{ product.name }}</div>`
})
export class ProductComponent implements OnInit {
  @Input() product!: Product;

  constructor(private analytics: AnalyticsService) {}

  ngOnInit(): void {
    // Only track if product is high-value
    if (this.product.price > 1000) {
      this.analytics.track('high_value_product_viewed', {
        product_id: this.product.id,
        product_name: this.product.name,
        price: this.product.price,
        category: this.product.category,
      });
    }
  }
}

Suivi des formulaires

Suivez les interactions et les soumissions de formulaires :

// components/contact-form/contact-form.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AnalyticsService } from '../../services/analytics.service';

@Component({
  selector: 'app-contact-form',
  template: `
    <form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
      <input
        type="text"
        formControlName="name"
        (focus)="onFieldFocus('name')"
        placeholder="Name"
      />
      <input
        type="email"
        formControlName="email"
        (focus)="onFieldFocus('email')"
        placeholder="Email"
      />
      <textarea
        formControlName="message"
        (focus)="onFieldFocus('message')"
        placeholder="Message"
      ></textarea>
      <button type="submit">Send Message</button>
    </form>
  `
})
export class ContactFormComponent {
  contactForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private analytics: AnalyticsService
  ) {
    this.contactForm = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      message: ['', Validators.required],
    });
  }

  onFieldFocus(fieldName: string): void {
    this.analytics.track('form_field_focused', {
      form_id: 'contact',
      field_name: fieldName,
    });
  }

  async onSubmit(): Promise<void> {
    if (this.contactForm.invalid) {
      return;
    }

    const formData = this.contactForm.value;

    // Track form submission
    this.analytics.track('contact_form_submitted', {
      form_id: 'contact',
      has_message: formData.message.length > 0,
    });

    // Submit form logic
  }
}

Suivi du commerce électronique

Suivez les vues de produits, les ajouts au panier et les achats :

// services/ecommerce-tracking.service.ts
import { Injectable } from '@angular/core';
import { AnalyticsService } from './analytics.service';

interface Product {
  id: string;
  name: string;
  category: string;
  price: number;
}

interface CartItem extends Product {
  quantity: number;
}

interface Cart {
  items: CartItem[];
  total: number;
}

interface Order extends Cart {
  id: string;
  tax: number;
  shipping: number;
}

@Injectable({
  providedIn: 'root'
})
export class EcommerceTrackingService {
  constructor(private analytics: AnalyticsService) {}

  trackProductView(product: Product): void {
    this.analytics.track('product_viewed', {
      product_id: product.id,
      product_name: product.name,
      category: product.category,
      price: product.price,
      currency: 'USD',
    });
  }

  trackAddToCart(product: Product, quantity: number = 1): void {
    this.analytics.track('product_added_to_cart', {
      product_id: product.id,
      product_name: product.name,
      quantity,
      price: product.price,
      total: product.price * quantity,
    });
  }

  trackCheckout(cart: Cart): void {
    this.analytics.track('checkout_started', {
      cart_total: cart.total,
      item_count: cart.items.length,
      currency: 'USD',
    });
  }

  trackPurchase(order: Order): void {
    this.analytics.trackPurchase({
      amount: order.total,
      currency: 'USD',
      product: `Order #${order.id}`,
    });

    this.analytics.track('purchase_completed', {
      order_id: order.id,
      revenue: order.total,
      tax: order.tax,
      shipping: order.shipping,
      currency: 'USD',
      items: order.items.map(item => ({
        product_id: item.id,
        quantity: item.quantity,
        price: item.price,
      })),
    });
  }
}

Utilisation dans un composant

// pages/product-detail/product-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { EcommerceTrackingService } from '../../services/ecommerce-tracking.service';

@Component({
  selector: 'app-product-detail',
  template: `
    <div *ngIf="product">
      <h1>{{ product.name }}</h1>
      <p>\${{ product.price }}</p>
      <button (click)="addToCart()">Add to Cart</button>
    </div>
  `
})
export class ProductDetailComponent implements OnInit {
  product: any;

  constructor(
    private route: ActivatedRoute,
    private ecommerceTracking: EcommerceTrackingService
  ) {}

  ngOnInit(): void {
    // Load product and track view
    this.route.params.subscribe(params => {
      // Fetch product by params.id
      this.product = {
        id: params['id'],
        name: 'Example Product',
        price: 99.99,
        category: 'Electronics',
      };

      this.ecommerceTracking.trackProductView(this.product);
    });
  }

  addToCart(): void {
    this.ecommerceTracking.trackAddToCart(this.product);
    // Add to cart logic
  }
}

Suivi des erreurs avec ErrorHandler

Suivez les erreurs automatiquement :

// services/global-error-handler.service.ts
import { ErrorHandler, Injectable } from '@angular/core';

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
  handleError(error: Error): void {
    // Track error via global zenovay object
    if ((window as any).zenovay) {
      (window as any).zenovay('track', 'error_occurred', {
        error_message: error.message,
        error_stack: error.stack,
        error_name: error.name,
        url: window.location.href,
        user_agent: navigator.userAgent,
      });
    }

    // Log to console
    console.error('Error caught by GlobalErrorHandler:', error);
  }
}

Enregistrez dans la configuration de l'application :

// app.config.ts
import { ApplicationConfig, ErrorHandler } from '@angular/core';
import { GlobalErrorHandler } from './services/global-error-handler.service';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: ErrorHandler, useClass: GlobalErrorHandler },
    // other providers
  ]
};

Identification des utilisateurs

Suivez les utilisateurs identifiés :

// services/user-tracking.service.ts
import { Injectable } from '@angular/core';
import { AnalyticsService } from './analytics.service';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class UserTrackingService {
  constructor(
    private analytics: AnalyticsService,
    private auth: AuthService
  ) {
    this.initUserTracking();
  }

  private initUserTracking(): void {
    this.auth.user$.subscribe(user => {
      if (user) {
        this.analytics.identify(user.id, {
          email: user.email,
          name: user.name,
          plan: user.subscription?.plan,
          signup_date: user.createdAt,
        });
      }
    });
  }
}

Initialisez dans le composant de l'application :

// app.component.ts
import { Component } from '@angular/core';
import { UserTrackingService } from './services/user-tracking.service';

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>',
})
export class AppComponent {
  constructor(private userTracking: UserTrackingService) {
    // Service automatically starts tracking on init
  }
}

Support TypeScript

Support TypeScript complet avec des événements typés :

// types/analytics.types.ts
export interface AnalyticsEvents {
  signup_started: {
    plan: 'free' | 'pro' | 'scale' | 'enterprise';
    source: string;
  };
  product_viewed: {
    product_id: string;
    product_name: string;
    price: number;
  };
  purchase_completed: {
    order_id: string;
    revenue: number;
    currency: string;
  };
}

// Declare the global zenovay function for TypeScript
declare global {
  interface Window {
    zenovay: (...args: any[]) => void;
  }
}

// Typed tracking service
import { Injectable } from '@angular/core';
import { AnalyticsEvents } from '../types/analytics.types';

@Injectable({
  providedIn: 'root'
})
export class TypedAnalyticsService {
  private get zenovay(): any {
    return (window as any).zenovay;
  }

  track<K extends keyof AnalyticsEvents>(
    eventName: K,
    data: AnalyticsEvents[K]
  ): void {
    if (this.zenovay) {
      this.zenovay('track', eventName, data);
    }
  }
}

Intégration RxJS

Exploitez les modèles réactifs d'Angular :

// services/analytics-rxjs.service.ts
import { Injectable } from '@angular/core';
import { AnalyticsService } from './analytics.service';
import { Observable, of } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AnalyticsRxjsService {
  constructor(private analytics: AnalyticsService) {}

  trackEvent$(eventName: string, data?: Record<string, any>): Observable<void> {
    return of(this.analytics.track(eventName, data)).pipe(
      tap(() => console.log(`[Analytics] Tracked: ${eventName}`)),
      catchError(error => {
        console.error('[Analytics] Error:', error);
        throw error;
      })
    );
  }
}

Optimisation des performances

Debounce du suivi

Empêchez le suivi excessif :

// components/search-box/search-box.component.ts
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { AnalyticsService } from '../../services/analytics.service';

@Component({
  selector: 'app-search-box',
  template: `
    <input
      type="search"
      [formControl]="searchControl"
      placeholder="Search..."
    />
  `
})
export class SearchBoxComponent {
  searchControl = new FormControl('');

  constructor(private analytics: AnalyticsService) {
    this.searchControl.valueChanges.pipe(
      debounceTime(500),
      distinctUntilChanged()
    ).subscribe(query => {
      if (query) {
        this.analytics.track('search_performed', {
          query,
          length: query.length,
        });
      }
    });
  }
}

Tests

Service d'analyse fictif dans les tests

// testing/analytics.mock.ts
import { Injectable } from '@angular/core';

@Injectable()
export class MockAnalyticsService {
  track = jasmine.createSpy('track');
  identify = jasmine.createSpy('identify');
  trackGoal = jasmine.createSpy('trackGoal');
  trackPurchase = jasmine.createSpy('trackPurchase');
}

Composant de test

// components/signup-button/signup-button.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SignupButtonComponent } from './signup-button.component';
import { AnalyticsService } from '../../services/analytics.service';
import { MockAnalyticsService } from '../../testing/analytics.mock';

describe('SignupButtonComponent', () => {
  let component: SignupButtonComponent;
  let fixture: ComponentFixture<SignupButtonComponent>;
  let analyticsService: MockAnalyticsService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [SignupButtonComponent],
      providers: [
        { provide: AnalyticsService, useClass: MockAnalyticsService }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(SignupButtonComponent);
    component = fixture.componentInstance;
    analyticsService = TestBed.inject(AnalyticsService) as any;
    fixture.detectChanges();
  });

  it('should track event on click', async () => {
    const button = fixture.nativeElement.querySelector('button');
    button.click();

    await fixture.whenStable();

    expect(analyticsService.track).toHaveBeenCalledWith('signup_started', {
      plan: 'professional',
      source: 'pricing_page'
    });
  });
});

Composants autonomes (Angular 17+)

Intégration complète avec les composants autonomes :

// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: '<router-outlet></router-outlet>',
})
export class AppComponent {}

Aucune configuration de module Angular spéciale n'est nécessaire. Le script de suivi Zenovay dans index.html gère tout. Votre AnalyticsService enveloppe simplement les appels window.zenovay et peut être injecté dans n'importe quel composant.

Configuration de l'environnement

// environments/environment.ts
export const environment = {
  production: false,
};

// environments/environment.prod.ts
export const environment = {
  production: true,
};

Le code de suivi est configuré directement dans la balise de script index.html. Pour différents environnements, vous pouvez utiliser la fonction de remplacement de fichiers d'Angular pour permuter les fichiers index.html, ou simplement utiliser le même code de suivi dans tous les environnements (Zenovay gère la séparation des environnements dans le tableau de bord).

Ressources supplémentaires

Cette page vous a-t-elle été utile ?