Angular2 : un chronomètre

Mis à jour pour la version rc1 !

Rien ne vaut les exemples alors je vous en propose un à partir de ce qu’on a vu dans l’article précédent. J’avais créé un chronomètre comme illustration de cet article sur vue.js. Je vous propose de réaliser le même chronomètre mais cette fois avec Angular2.

Cet exemple va nous permettre d’utiliser pleinement les possibilités de TypeScript et aussi de découvrir une nouvelle directive d’Angular.

J’ai mis le code final de cet article en téléchargement ici.

Le fonctionnement

Pour résumer le cahier des charges je rappelle le fonctionnement désiré.

Au départ le chronomètre se présente ainsi :

img026

Lorsqu’on clique sur le bouton « Démarrer » le chronomètre se met en marche. Au niveau des boutons « Démarrer » disparaît et « Arrêter » apparaît :

img027

Lorsqu’on clique sur « Arrêter » le comptage s’interrompt, le bouton « Arrêter » disparaît. Les boutons « Effacer » et « Continuer » apparaissent :

img028

Si on clique sur « Effacer » on revient à la situation de départ et si on clique sur « Continuer » le comptage reprend et le bouton « Arrêter » réapparaît.

La page index.html

Pour la page HTML il n’y a rien de bien particulier et on va retrouver ce qu’on a vu dans les articles précédents. Les seules différences sont :

  • le chargement de Bootstrap pour la mise en forme du style,
  • le nom du composant qu’on va créer : chronometre.

Voici le code :

<html>
  <head>
    <title>Mon Projet</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">

    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <chronometre>Chargement...</chronometre>
  </body>
</html>

Organisation du code

Comme le code de l’application doit être modulaire pour respecter les bonnes pratiques je vais l’organiser en plusieurs fichiers ainsi :

img025

Avec ces missions :

  • main : assure le démarrage, on a déjà analysé ce fichier dans les précédents articles,
  • app.component : c’est le composant de base de l’application,
  • app.component.html : c’est le template du composant,
  • timer : une classe chargée de gérer le temps,
  • state : une classe chargée de gérer l’état des boutons.

Le bootstrap (main.ts)

La c’est le plus simple puisque c’est ce qu’on a déjà vu précédemment :

import { bootstrap }    from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

bootstrap(AppComponent);

Deux importations et le lancement.

Le timer (timer.ts)

Le timer doit :

  • exposer les propriétés minutes et secondes
  • pouvoir démarrer (start)
  • pouvoir stopper (stop)
  • pouvoir être réinitialisé (reset)

Voici son code :

export class Timer {

  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;
  }
}

L’approche objet permise par TypeScript permet de travailler proprement par rapport au JavaScript que l’on utilise habituellement !

Les 2 propriétés sont privées et j’ai prévu des accesseurs (get).

Trois méthodes assurent le fonctionnement du timer.

Remarquez l’utilisation d’une fonction fléchée dans la méthode start qui permet de pouvoir utiliser this de façon lexicale. L’utilisation de this est souvent un souci avec JavaScript, en particulier dans une fonction anonyme. Avec une fonction fléchée on se retrouve avec un fonctionnement plus classique.

Les états (state.ts)

Pour la gestion de la visibilité des boutons selon l’état du chronomètre j’ai aussi prévu une classe avec 3 propriétés :

export class State {

  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;		
  }
}

On a des accesseurs pour exposer les propriétés qui sont privées.

On a 3 méthodes pour gérer les 3 états nécessaires.

Le template (app.component.html)

Au niveau du template on a ce code :

<div class="container">
    <div class="text-center">
        <h1>Le chronomètre</h1>
        <hr>
        <h1>
            <span class="label label-primary">{{_timer.minutes}}</span> minutes
            <span class="label label-primary">{{_timer.secondes}}</span> secondes
        </h1>
        <br>
        <p>
            <button class="btn btn-danger btn-lg" *ngIf="_state.backward" (click)="backward()">Effacer</button>
            <button class="btn btn-danger btn-lg" *ngIf="_state.stop" (click)="stop()">Arrêter</button>
            <button class="btn btn-primary btn-lg" *ngIf="_state.play" (click)="play()">{{_btnPlay}}</button>
        </p>
    </div>
</div>

Pour l’affichage des minutes et secondes on a des interpolations classiques :

<span class="label label-primary">{{_timer.minutes}}</span> minutes
<span class="label label-primary">{{_timer.secondes}}</span> secondes

Par contre le code des boutons mérite quelques commentaires :

<button class="btn btn-danger btn-lg" *ngIf="_state.backward" (click)="backward()">Effacer</button>
<button class="btn btn-danger btn-lg" *ngIf="_state.stop" (click)="stop()">Arrêter</button>
<button class="btn btn-primary btn-lg" *ngIf="_state.play" (click)="play()">{{_btnPlay}}</button>

La directive *ngIf permet de rendre la balise (et évidemment tout le DOM inclus) visible si la valeur entre les guillemets est true, et invisible si la valeur est false. Ici j’utilise les 3 états de la classe vue ci-dessus pour la gestion de cette visibilité.

On retrouve pour chaque bouton la gestion du clic comme on l’a vu dans le précédent article.

Le composant (app.component.ts)

Le composant est le chef d’orchestre de l’ensemble. Son code est léger et clair parce qu’il fait appel à d’autres modules :

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

@Component({
    selector: 'chronometre',
    templateUrl: 'app/app.component.html'
})
export class AppComponent {
    private _btnPlay: string = 'Démarrer';
    private _timer: Timer = new Timer();
    private _state: State = new State();
    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';
    }
}

On a 3 importations :

import { Component } from '@angular/core';
import { Timer } from './timer';
import { State } from './state';
  • angular : pour avoir la gestion de base du composant,
  • timer : pour la gestion du temps,
  • state : pour la gestion des boutons.

Ensuite les annotations définissent le sélecteur et le template :

@Component({
  selector: 'chronometre',
  templateUrl: 'app/app.component.html'
})

Cette fois pour le template on a une url au lieu d’avoir directement le code présent ici.

On a ensuite la classe du composant avec 3 propriétés privées :

private _btnPlay: string = 'Démarrer';
private _timer: Timer = new Timer();
private _state: State = new State();
  • btnPlay : le texte du bouton de démarrage-continuation,
  • timer : une référence du timer,
  • state : une référence de la classe des états.

Pour finir on a les 3 méthodes pour les 3 actions :

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';  
}

Même si Angular a une infrastructure lourde il permet d’écrire du code simple et clair !

On poursuivra cette exploration dans un prochain article !

Laisser un commentaire