Autour du codeDevelopper toujours mieux
Posté le

Creation d'une application Angular2

C'est officiel, Angular 2, le framework frontend de Google vient de sortir en version stable. voici donc un petit tutoriel dans lequel on va créer une application avec Angular 2 en prenant pour inspiration la série SG1.

Première mission installer nodeJS qui va ensuite nous permettre d'installer les modules dont dépend une application Angular2.

J'ai choisi la version 4.5 de nodeJS. Si comme moi vous êtes sous Debian voici la ligne de commande que vous pouvez utiliser:

# curl -sL https://deb.nodesource.com/setup_4.x | bash -

# apt-get install -y nodejs

Sinon, je vous laisse consulter la doc d'installation de nodeJS

npm est en version 2 avec nodeJS 4.5. Il va nous falloir la version 3 pour éviter des messages d'erreur lors de l'installation d'Agular 2.

# npm install -g npm3

On va créer un répertoire sg1 pour contenir notre projet puis dans ce répertoire, on va générer le fichier package.json.

mkdir sg1

cd sg1

npm init -y

On va ensuite installer le compilateur TypeScript ainsi que typings.

$ npm install --save-dev typescript@2.0.2 typings@1.3.3

  • TypeScript est un langage typé et compilé en JS via la commande tsc.
  • Typings remplace tsd, il permet de connaitre les types utilisés et retournés par les fonctions des librairies en JS qui seront utilisé dans du code TypeScript.

On va lancer le compilateur TypeScript une première fois avec l'option --init cela va créer un fichier tsconfig.json dans lequel les options de compilation vont être enregistrées. Ce qui nous évitera de toutes les re-tapper à chaque fois que l'on travail sur le projet.

$ tsc --init --t es5 -m commonjs --moduleResolution node --sourceMap --emitDecoratorMetadata --experimentalDecorators --removeComments

Installation d'Angular 2

Angular 2 est décomposé en une dizaine de modules, ce qui permet d'installer seulement ce que l'on utilise.

$ npm install --save @angular/common@2.0.0 @angular/core@2.0.0 @angular/compiler@2.0.0 @angular/platform-browser@2.0.0 @angular/platform-browser-dynamic@2.0.0

Angular 2 est également dépendant d'autres modules pour fonctionner. Certaine de ces dépendances comme systemJS ou core.js disparaitront avec l'arrivé d'ES6

$ npm install --save core-js@2.4.1 reflect-metadata@0.1.3 rxjs@5.0.0-beta.12 systemjs@0.19.27 zone.js@0.6.23

On va installer le fichier de définition de type de core-js.

$ typings install core-js --global --source dt --save

Créer le point d'entrée d'une application Angular 2

On va créer un répertoire app qui contiendra notre application.

$ mkdir app

Dans ce répertoire, on va créer deux fichiers app.module.ts et main.ts. Le fichier app.module.ts contiendra la classe AppModule qui est le point d'entrée de notre application. Le fichier main.ts contient le code pour démarrer l'AppModule dans un navigateur.

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

@NgModule({
    imports: [ BrowserModule ]
})
export class AppModule { }

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Configurer SystemJS

SystemJS permet d'utiliser est de charger des modules JavaScript normalisé ES6 dans du code ES5. Pour utiliser SystemJS il faut l'importer dans notre index.html ainsi que son fichier de configuration. Le fichier de configuration sera nommé systemjs.config.js et sera à la racine de notre projet. Il indique à SystemJS quel fichier doit être chargé à chaque instruction require est trouvée dans un fichier JS. Ainsi pour l'instruction require('@angular/core') SystemJS utilisera node_modules/@angular/core/bundles/core.umd.js

On va également créer notre index.html qui importera angular et SystemJS.

(function (global) {
  System.config({

    /* Cette map permet d'indiquer quel fichier il faut charger
     * en fonction d'un nom du module importé.
     */
    map: {
      'app': 'app',
      '@angular/core': 'node_modules/@angular/core/bundles/core.umd.js',
      '@angular/common': 'node_modules/@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'node_modules/@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'node_modules/@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      'rxjs': 'node_modules/rxjs'
    },

    /* Le point d'entrée du module app est main.js et
     * l'extension des fichiers est .js.
     */
    packages: {
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });
})(this);

<html>
  <head>
    <title>SG 1</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <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>
  </body>
</html>

Créer notre premier composant Angular 2

Les composants sont les briques de base d'Angular 2, C'est une classe qui encapsule un peu de logiques métiers. Cette classe sera décorée avec le décorateur component qui définira un template pour afficher la classe.

Dans un fichier app.sgc.ts on va créer le composant StargateCommand qui définie un tableau members avec les personnages de la serie de SG1. Le decorateur Component permet de définir le selector. C'est le nom de la balise que l'on va utiliser pour créer notre composant. Dans le template, la directive ngFor va nous permettre de boucler sur les élément contenue dans members et la dirrective [style.color] permet d'aller modifier dirrectement un attribut html. Ici on demande à ce que la couleur de la balise span soit verte si member.health est supérieure à 5 sinon rouge.

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

@Component({
    selector: 'sgc',
    template: `
        <ul>
            <li *ngFor="let member of members">
                <span [style.color]="member.health > 5 ? 'green' : 'red'" >{{ member.name }}</span>
            </li>
        </ul>
    `
})
export class StargateCommand {
    members: Array<any> = [
        {name: "Jack O'neal",
         health: 10},
        {name: "Daniel Jackson",
         health: 5},
        {name: "Samantha Carter",
         health: 10},
        {name: "Teal'c",
         health: 4},
        {name: "Vala Mal Doran",
         health: 9},
        {name: "Janet Fraiser",
         health: 9},
        {name: "Cameron Mitchell",
         health: 10}
    ];
}

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <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>
    <sgc>Loding ...</sgc>
  </body>
</html>

import { NgModule }        from '@angular/core';
import { BrowserModule }   from '@angular/platform-browser';
import { StargateCommand } from './app.sgc';

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ StargateCommand ],
    bootstrap:    [ StargateCommand ]
})
export class AppModule { }

Une fois que l'on a créé notre composant, on va l'importer dans la classe AppModule. Puis l'utiliser dans l'index.html Il nous reste plus qu'à compiler notre application.

tsc -w

L'option -w sert à relancer la compilation à chaque fois qu'un fichier a été modifié puis à lancer un serveur.

python3 -m http.server

Vous pouvez maintenant visualiser votre application à l'adresse http://127.0.0.1:8000/

Passer des données du composant parent vers un composant enfant

On va créer un fichier app.teammember.ts dans lequel on va définir un composant TeamMember. Puis on va déplacer la logique d'un membre d'équipe dans ce composant. Le composant parent va passer le nom en entrée du composant fils via des attributs. Pour dire qu'une propriété d'un composant est définissable par des attributs en entrée, on utilise le décorateur @Input

On va ajouter notre composant TeamMember dans le tableau declarations d'AppModule pour que l'on puisse l'utiliser au sein de l'application. Dans StargateCommand on va importer TeamMember et l'utiliser dans le template.

import { Component, Input } from '@angular/core';

@Component({
    selector: 'team-member',
    template: `
        <span [style.color]="health > 5 ? 'green' : 'red'" >{{ name }}</span>
    `
})
export class TeamMember {
    @Input() name: string;
    @Input() health: number;
}

import { Component }  from '@angular/core';
import { TeamMember } from './app.teammember';


@Component({
    selector: 'sgc',
    template: `
        <ul>
            <li *ngFor="let member of members">
                <team-member [name]="member.name"
                             [health]="member.health"></team-member>
            </li>
        </ul>
    `
})
export class StargateCommand {
    members: Array<any> = [
        {name: "Jack O'neal",
         health: 10},
        {name: "Daniel Jackson",
         health: 5},
        {name: "Samantha Carter",
         health: 10},
        {name: "Teal'c",
         health: 4},
        {name: "Vala Mal Doran",
         health: 9},
        {name: "Janet Fraiser",
         health: 9},
        {name: "Cameron Mitchell",
         health: 10}
    ];
}

import { NgModule }        from '@angular/core';
import { BrowserModule }   from '@angular/platform-browser';
import { StargateCommand } from './app.sgc';
import { TeamMember }      from './app.teammember';


@NgModule({
    imports: [ BrowserModule ],
    declarations: [ StargateCommand, TeamMember ],
    bootstrap:    [ StargateCommand ]
})
export class AppModule { }

Écouter un événement

Pour écouter un événement on met le nom de celui-ci entre deux parenthèses et on met comme valeur le nom d'une fonction à appeler.

Dans notre exemple, chaque membre de l'équipe aura un bouton pour l'envoyer en mission il reviendra de mission au bout de 2 seconde. Pour réaliser cela on va ajouter deux méthodes a la classe TeamMember.

  • exploreTheGalaxy qui va désactivé le bouton.
  • endOfMission qui est appelé deux secondes après l'appel d'exploreTheGalaxy et qui réactive le bouton.
import { Component, Input } from '@angular/core';

@Component({
    selector: 'team-member',
    template: `
        <span [style.color]="health > 5 ? 'green' : 'red'" >{{ name }}</span>
        <button (click)="exploreTheGalaxy()" [disabled]="!ready">{{ labelButton }}</button>
    `
})
export class TeamMember {
    @Input() name: string;
    @Input() health: number;

    ready: boolean = true;
    labelButton: string = 'Explore the galaxy';

    endOfMission = function() {
        this.ready = true;
        this.labelButton = 'Explore the galaxy';
    };

    exploreTheGalaxy() {
        if (this.ready) {
            this.ready = false;
            this.labelButton = 'Despatched';
            setTimeout(() => this.endOfMission(), 2000);
        }
    }
}

Créer c'est propre événement

Un composant peut recevoir des données en entrée par un composant parent, mais il peut également notifier un composant parent d'un changement d'état. Pour ce faire on va utiliser le décorateur @Output pour indiquer quel événement un composant parent peut écouter. Les événements doivent être des objets de type EventEmitter

Dans notre exemple un composant TeamMember enverra un événement MissionDone lorsqu'il rentrera de mission.

Le composant StargateCommand aura une méthode onMissionDone qu'il va lier à l'événement. la méthode onMissionDone affichera le nom de la personne qui vient de rentrer.

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
    selector: 'team-member',
    template: `
        <span [style.color]="health > 5 ? 'green' : 'red'" >{{ name }}</span>
        <button (click)="exploreTheGalaxy()" [disabled]="!ready">{{ labelButton }}</button>
    `
})
export class TeamMember {
    @Input() name: string;
    @Input() health: number;
    @Output() missionDone = new EventEmitter<TeamMember>();

    ready: boolean = true;
    labelButton: string = 'Explore the galaxy';

    endOfMission = function() {
        this.ready = true;
        this.labelButton = 'Explore the galaxy';
        this.missionDone.emit(this);
    };

    exploreTheGalaxy() {
        if (this.ready) {
            this.ready = false;
            this.labelButton = 'Despatched';
            setTimeout(() => this.endOfMission(), 2000);
        }
    }
}

import { Component }  from '@angular/core';
import { TeamMember } from './app.teammember';


@Component({
    selector: 'sgc',
    template: `
        <p>{{ message }}</p>
        <ul>
            <li *ngFor="let member of members">
                <team-member [name]="member.name"
                             [health]="member.health"
                             (missionDone)="onMissionDone($event)"></team-member>
            </li>
        </ul>
    `
})
export class StargateCommand {

    message:string;
    onMissionDone(teamMember) {
        this.message = teamMember.name + " had just returned from a mission";
    }

    members: Array<any> = [
        {name: "Jack O'neal",
         health: 10},
        {name: "Daniel Jackson",
         health: 5},
        {name: "Samantha Carter",
         health: 10},
        {name: "Teal'c",
         health: 4},
        {name: "Vala Mal Doran",
         health: 9},
        {name: "Janet Fraiser",
         health: 9},
        {name: "Cameron Mitchell",
         health: 10}
    ];
}

Voilou j'espère que ça vous a plu et que la quantité de librairie à installer ne vous a pas découragé. En attendant un prochain tuto, à vos IDE et bon code laughing