Angular2 : les services

Mis à jour avec la version rc1 !

Dans le dernier article on a vu un chronomètre réalisé avec Angular2. Dans le présent article on va revenir sur ce code en se posant quelques questions et en y apportant des réponses. On va ainsi mettre en lumière l’intérêt d’utiliser l’injection de dépendance qui a une place importante dans l’organisation d’Angular2.

Le code final de cet article est téléchargeable ici.

Le problème

Dans le code du composant réalisé précédemment (app.component.ts) on a cette propriété :

private _timer: Timer = new Timer();

On déclare une propriété privée , de type Timer et on l’affecte avec un nouvel objet de type Timer. Cela fonctionne parfaitement bien mais…

On peut repprocher à cette approche plusieurs inconvénients :

  • le composant a besoin de connaître comment initialiser le timer, en particulier renseigner correctement ses paramètres éventuels. Notre timer ici est très simple et n’a besoin d’aucun paramètre mais il pourrait évoluer à l’avenir. Imaginez qu’on l’utilise dans plusieurs composants, il faudrait alors reprendre tous ces codes et ne pas en oublier !
  • le fait d’utiliser new crée une nouvelle instance du timer. Si on a besoin de ce timer dans un autre composant on va créer une nouvelle instance. Selon les cas cela peut être judicieux, mais ça peut aussi être improductif pour une classe qui exige des ressources importantes.
  • on impose un certain timer au composant, on peut imaginer que dans l’avenir on change d’optique.
  • on aura du mal à faire des tests pour ce composant.

La solution

Le composant et l’injection de dépendance

C’est ici qu’intervient l’injection de dépendance. Plutôt que de demander au composant de créer lui-même l’objet on va se contenter de lui injecter un objet déjà tout prêt !

On va donc supprimer la propriété qu’on avait prévue et on va ajouter un constructeur pour l’injection :

private _btnPlay: string = 'Démarrer';
//private _timer: Timer = new Timer();
private _state: State = new State();

constructor(private _timer: TimerService) { }

La variable locale est maintenant créée au niveau du constructeur et on lui attribut le type TimerService. Lorsqu’il va créer le composant Angular saura qu’il doit injecter ici une instance de TimerService. Mais pour le moment il ne connaît pas ce service et n’a aucune idée de comment l’utiliser…

Angular est équipé d’un injecteur de dépendances qui possède un conteneur (container) qui contient… tous les service nécessaires.

Pour que ça fonctionne il faut ajouter un provider dans le composant :

@Component({
  selector: 'chronometre',
  templateUrl: 'app/app.component.html',
  providers: [TimerService]
})
export class AppComponent {

Vous voyez la ligne :

providers: [TimerService]

Ici on demande à Angular de créer une instance de TimerService quand il crée le composant.

Le nouveau code complet du composant est maintenant celui-ci :

import { Component} from '@angular/core';
import { TimerService } from './timer.service';
import { State } from './state';

@Component({
  selector: 'chronometre',
  templateUrl: 'app/app.component.html',
  providers: [TimerService]
})
export class AppComponent {

  private _btnPlay: string = 'Démarrer';
  private _state: State = new State();

  constructor(private _timer: TimerService) { }

  play() {
    this._timer.start();
    this._state.setPlay();
    this._btnPlay = 'Continuer';
  }	
  stop() {
    this._timer.stop();
    this._state.setStop();
  }
  backward() {
    this._timer.reset();
    this._state.setBackward();
    this._btnPlay = 'Démarrer';  
  }
}

Le service TimerService

Voyons à présent notre timer avec son nouveau nom de fichier (timer.service.ts). Et voici le nouveau code :

import { Injectable } from '@angular/core';

@Injectable()
export class TimerService {

    private _minutes: number = 0;
    private _secondes: number = 0;
    private _totalSecondes: number = 0;
    private _timer;

    get minutes(): number { return this._minutes; }
    get secondes(): number { return this._secondes; }

    start() {
        this._timer = setInterval(() => {
            this._minutes = Math.floor(++this._totalSecondes / 60);
            this._secondes = this._totalSecondes - this._minutes * 60;
        }, 1000);
    }
    stop() {
        clearInterval(this._timer);
    }
    reset() {
        this._totalSecondes = this._minutes = this._secondes = 0;
    }
}

Pour que notre service soit injectable il faut l’indiquer à Angular :

import { Injectable } from 'angular2/core';

D’autre part on lui ajoute une annotation :

@Injectable()
export class TimerService {

Dans notre cas c’est parfaitement inutile parce qu’on à rien de particulier,  aucune injection à effectuer dans notre service. Mais pour des raisons de bonne pratique on ajoute tout de même ce code pour l’utiliser au besoin.

Et voilà, notre timer est maintenant un service injectable !

Un second service

On ne va pas s’arrêter en si bon chemin et on va aussi injecter le service pour les états.

On crée un nouveau fichier (state.service.ts) avec ce code :

import { Injectable } from '@angular/core';

@Injectable()
export class StateService {

  private _play: boolean = true;	
  private _stop: boolean = false;
  private _backward: boolean = false;

  get stop(): boolean { return this._stop; }
  get backward(): boolean { return this._backward; }
  get play(): boolean { return this._play; }

  setPlay() {
    this._stop = true;
    this._play = this._backward = false;		
  }
  setStop() {
    this._stop = false;
    this._play = this._backward = true;		
  }
  setBackward() {
    this._play = true;
    this._stop = this._backward = false;		
  }
}

Et on modifie le composant (app.component.ts) pour lui injecter aussi ce service :

import { Component } from '@angular/core';
import { TimerService } from './timer.service';
import { StateService } from './state.service';

@Component({
  selector: 'chronometre',
  templateUrl: 'app/app.component.html',
  providers: [TimerService, StateService]
})
export class AppComponent {

  private _btnPlay: string = 'Démarrer';

  constructor(private _timer: TimerService, private _state: StateService) { }

  play() {
    this._timer.start();
    this._state.setPlay();
    this._btnPlay = 'Continuer';
  }	
  stop() {
    this._timer.stop();
    this._state.setStop();
  }
  backward() {
    this._timer.reset();
    this._state.setBackward();
    this._btnPlay = 'Démarrer';  
  }
}

Et voilà deux injections !

 

 

Laisser un commentaire