Patrice Ferlet
Patrice Ferlet
Créateur de ce blog.
Publié le mars 6, 2016 Temps de lecture: 11 min

Me suivre sur Mastodon

Introduction à Angular2

Angular2 est en phase “beta” depuis maintenant quelques semaines et je pense qu’il est temps de parler de mon expérimentation sur le sujet. Je ne vais pas vous proposer une documentation mais plutôt un genre de “howto” de mon point de vue.

Angular 2 - From binding

Je ne vais pas vous faire un manuel ou un “cookbook”, mais juste un retour sur mon expérience qui vous permettra de démarrer sans trop de douleurs avec Angular 2.

Attention, je vais beaucoup vulgariser, résumer et il se peut que je dérive de la réalité. Donc, amis experts de Angular 2, ne tenez pas compte de ces raccourcis, et amis débutants, ne prenez pas tout comme parole d’évangile.

Sachez avant tout que j’utilise Docker pour éviter de saloper mon système avec un paquet de dépendances node, et l’image est récupérable via:

docker pull metal3d/ng

Tout est expliqué sur la page de mon image docker.

Angular2

J’ai aujourd’hui pas mal d’expérience sur Angular 1. J’apprécie énormément les principes qu’a apporté ce framework. D’abord parce que ce n’est pas une “librairie” en soit, qu’il impose une certaine manière de faire et que l’injection de dépendances permet de ne pas faire du code noué.

Passer à Angular2 a été douloureux pendant les premières heures. Car au lieu de faire une évolution, les développeurs de Angular ont littéralement changé le framework. Ça peut surprendre au premier regard, et le fait de passer à un autre langage (typescript) a une tendance à faire tomber dans les pâmes (et non pas dans les pommes) certaines personnes.

Typescript n’est pas le seul langage que l’on peut utiliser. En fait, on peut toujours utiliser javascript. Mais je ne vous le conseille pas. Il existe aussi Dart, mais je n’ai pas encore assez de recul sur le langage pour en parler. On va donc parler essentiellement de Typescript.

Les décorateurs

C’est un concept très connu en Java, en Python, en C#, etc. Mais absolument inconnu en Javascript (bien qu’il existe des manières de faire). Un décorateur est un pattern et une notation qui permet de modifier un comportement ou des attributs d’un élément du code (classe, méthode, propriété) et ce via une fonction décoratrice.

Comme dans la plupart des langages, Typescript utilise la notation “@Foo” où “Foo” est le décorateur.

Exemple:

@Foo
class Bar {

}

Ici, “Bar” est décoré par “Foo”. En fait le décorateur précède simplement l’élément à décorer.

Je ne vais pas vous expliquer comment créer un décorateur, je tenais juste à préciser qu’ils existent et qu’ils sont fortement utilisés sur Angular2. Il ne faut retenir qu’une chose: un décorateur modifie un type pour lui attribuer des comportements. Et ce sera le cas pour transformer une classe en Component, en Injectable, etc.

Une dernière chose, il est possible d’utiliser plusieurs décorateurs en multipliant les annotations:

@Foo
@Bar
class Baz {
    // classs décorée par Foo et Bar

    @admin
    delete(){
        // ... méthode décorée par @admin
        // par exemple pour interdire son utilisation
        // à moins d'être loggué en tant qu'administrateur
    }

}

Component

Si vous comprenez ce qu’est un component, vous avez déjà compris 50% du fonctionnement de Angular 2. Je ne plaisante pas, c’est clairement la notion à comprendre.

Pratiquement tout ce que vous allez développer en Angular2 sera des “Components”. Vous pouvez traduire ça en “Composant”.

Angular2 bénéficie de la possibilité en Typescript d’utiliser des décorateurs (annotations), et c’est de cette manière qu’une classe devient un “Component”. Pour résumer, vous aurez ce genre de code:

import {Component} from 'angular2/core';

@Component({
    // des trucs ici
})
export class MaClasse {
    // et ici le contenu de la classe
}

Car effectivement, en Typescript, on utilise des classes, des vraies.

Mais que propose un Component ?

Et bien sachez qu’ils existaient en Angular 1, la preuve ici - un component est un type de directive très simple qui se configure d’une traite. En Angular2 c’est devenu le composant de base.

En gros, pour résumer (c’est le but de l’article hein), un Component défini une directive, son controlleur, les styles, la vue… et voilà.

Pour vous donner un exemple:

@Component({
    selector: 'mycomponent',
    template: '<div>...</div>',
    // ou
    templateurl: 'app/components/mycomponent/view.html'
})
export class AnExample{}

Les routes

Ce qui m’a dérouté (sans jeu de mot) c’est le fonctionnement des routes. Je ne dis pas que c’est compliqué, mais je me suis fait avoir par le client angular…

En effet, “angular-cli” permet de créer des routes en tapant:

ng generate route example

Or, une route, du point de vue du client angular, est une “jeu d’urls”, c’est à dire qu’il propose en gros le principe d’état de “ui.router” dans Angular 1.

Et honnêtement, nettoyer le code généré par angular-cli est tout bonnement infâme. Je peux paraitre peut-être méchant là, mais je vous assure que la première fois c’est loin d’être marrant.

Ce que j’ai compris c’est qu’il ne faut pas générer des routes pour tout et n’importe quoi.

Pour résumer, générer une route équivaut à générer un chemin qui aura des descendants, par exemple “/heroes’ pour lister les héros, puis “/heroes/:id” pour voir le détail d’un héro en particulier

Le client génère un Component “racine” qui va permettre de router plusieurs uri vers des components. Donc effectivement, pour vous “montrer un exemple”, le client Angular va générer pas mal de choses:

  • un Component racine (XxxRoot)
  • un Component liste (XxxList)
  • une classe de service pour lister des “items”
  • deux chemins, un pour la racine, et un pour la liste (formulaire qui modifie les items du service)

Donc, je vous préviens, c’est une liste de fichiers générés qu’il faut aller manipuler, nettoyer, etc…

Ce qui m’a posé problème, c’est de savoir comment faire pour que que l’url “/heroes” pointe sur le composant HeroesRoot. Et bien c’est vraiment tout con.

Dans le code du projet (project.ts) il faut:

  • importer HeroesRoot
  • ajouter la route dans le décorateur RouteConfig
  • et bha c’est tout hein.

Petite subtilité, puisque que HeroesRoot est un “router” qui permet de faire descendre des sous-routes, il faut suffixer la route par “…”. Pour une route vers un component simple, ce n’est pas le cas. Voyez l’exemple:

import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {HeroesRoot} from './heroes/heroes-root.component';
import {Hello} from './components/hello/hello';
//...

@Component({//...
})
@RouteConfig([
    // Cas d'une route "heroes" générée avec
    // $ ng generate route heroes
    // Le chemin fini par "..." ce qui signifie qu'il y aura des "sous-éléments"
    {path: '/heroes/...', component: HeroesRoot, name: 'Heroes', useAsDefault: true},

    // Cas d'une url vers un component généré avec
    // $ ng generate component hello
    {path: '/hello', component: Hello, name: 'Hello'}
])
export class Project {//...
}

Form

Les formulaires avec Angular 2 et surtout le “binding” de model est quant à lui, pour ma part, un bonheur sans nom. Exit l’utilisation de “$scope”, tout se passe coté classe et html sans aucune peine.

Prenons un exemple:


@Component({
    template: `
    <form (submit)="onSubmit()">
        <input type="text" [(ngModel)]="user.login" />
        <input type="password" [(ngModel)]="user.password" />
        <input type="submit" value="OK" />
    </form>
    <pre>
    {{ user | json }}
    </pre>
    `
})
export class User{
    user = {
        login: "foo",
        password: "bar"
    }

    onSubmit(){
        console.log("USER:", this.user);
    }
}

On décrypte.

D’abord, le template. Vous avez remarqué le “(submit)” ? Angular propose une notation de template déroutante de prime abord, mais aggréable quand on la comprend:

  • [X]="Y"” permet de changer l’attribut “X” de l’élément avec la valeur “Y” du Component.
  • (X)="Y"” permet de binder Y sur l’évènement “X”

Donc, ici, quand le formulaire est soumis, l’évènement “submit” appelera la méthode “onSubmit” de mon Component.

Mélangeons les deux bindings:

  • [(X)]=Y” va donc naturellement faire un “two way data binding”

Et on le voit bien avec “ngModel” (directive Angular 2) qui va modifier la propriété “user” de mon component, et inversement si le component modifie la propriété “user” alors la vue sera notifiée.

Car, oui, Angular 2 soulage fortement la notion de “scope” en le supprimant purement et simplement. La vue est directement connecté à la classe lors de la décoration “Component”.

Angular 2 me paraissait super abstrait à ce sujet (il y a encore peu d’article sur le net qui parle de Angular 2), mais à force de tester j’ai vite compris qu’il ne faut pas chercher midi à quatorze heure. Faire au plus simple, et aggrémenter après.

Reste le dernier point que je veux traiter dans cet article, le requêtage HTTP.

Http

Honnêtement, là, j’ai passé des plombes à lire et relire des didacticiels tous plus ou moins compliqué pour finalement me rendre compte que tout est super simple. Ce que je n’avais tout simplement pas assimilé à la base, c’est la notation de lambda

Pourtant, j’en ai bouffé de la lambda en Python. Mais connement je n’ai pas pris en compte un concept important: Typescript est compatible ES6. Et bha ouais… Quand on ne prend pas ça en compte, tous les exemples que vous lirez vous rendrons la vie dure. Et je n’avais pas compris que la notation que j’avais sous les yeux était une notation de lambda.

Mais c’est quoi une lambda ?

C’est tout simplement une “fonction sans corps”. Oui je résume. En gros, avant, au temps de Javascript ES5, vous aviez un lot de “callback” du genre:

obj.onSubmit(function(data){
    obj.result = data.name
})

En Typescript (et ES6), vous pouvez faire:

obj.onSubmit( data => this.result = data);

Ce qui revient exactement au même !

Là où ça devient intéressant, c’est que vous pouvez désormais avoir une notation plus claire pour réagir à des évènements, et c’est le cas pour la classe Http proposé par Angular.

Voici un exemple de Component qui embarque la vue et l’appel à une API - bien que je vous conseille de séparer l’appel dans un service:

import {Component} from 'angular2/core",
import {Http, Response} from 'angular2/http';

@Component({
    template: `
        <form (submit)="onSubmit">
            <input type="text" [(ngModel)]="model.data" />
            <input type="submit" value="Send" />
        </form>
    `
})
export class Example{
    // le model dans le formulaire
    model = {data:""}

    // resultat
    res = ""

    // constructor, ici on crée 
    // une propriété "this.http" de type Http (injectée)
    constructor(private http: Http){}

    // lors d'un submit de formulaire
    onSubmit(){
        // on utilise this.http, on appelle l'url "/api/url"
        this.http.post("/api/url", JSON.stringify(model))
            .subscribe(res => this.res = res.json())
    }
}

La méthode “subscribe” attend une fonction (en premièr argument) qui prend en argument les données retournées par l’appel. Au lieu de faire une fonction, j’utilise une “lambda” qui assigne à “this.res” le résultat décodé. L’équivalent serait:

export class Example{
    // le model dans le formulaire
    model = {data:""}

    // resultat
    res = ""

    // constructor, ici on crée 
    // une propriété "this.http" de type Http (injectée)
    constructor(private http: Http){}

    // lors d'un submit de formulaire
    onSubmit(){
        // on utilise this.http, on appelle l'url "/api/url"
        this.http.post("/api/url", JSON.stringify(model))
            .subscribe(function(res){
                this.res = res.json();
            })
    }
}

Vous n’ête pas forcé d’utiliser une lambda, il se peut que justement vous ayez besoin d’une méthode pour faire effectuer plusieurs opérations. Mais dans le principe, il est souvent très pratique d’utiliser une notation lambda.

Si vous ne voulez pas vous occuper de l’argument, ou simplement que la fonction attendue n’en utilise pas, vous devez utiliser “()” en guise d’argument:

obj.onFoo(() => console.log("bar"));

Du moment que vous comprennez qu’une lambda est une forme simplifiée de noter une fonction “inline”, alors tout s’éclaire. Ainsi, une manière de faire un formulaire d’authentification pourrait utiliser ce genre de code:


export class Auth{
    constructor(private http: Http){}
    auth = {
        login: "",
        pass : ""
    }

    onSubmit(){
        // subscribe prend en fait 3 arguments:
        // - onsucess
        // - onerror
        // - ondone
        this.http.Post("/login", this.auth)
            .subscibe(
                res => this.onLogged(res.json()),
                err => this.handleError(err),
                () => console.log("loggin done")
            )
    }

    onLogin(res){
        // res est décodé via la lambda, j'ai bien un objet
        window.localStorage.setItem('token', res.token);
        //... etc ...
    }

    handleError(err){
        console.error(err);
    }
}

Screencast

J’ai fait rapidement un petit screencast pour vous montrer comment on peut rapidement faire fonctionner Angular 2. J’ai réduit le temps d’init du projet dans la vidéo dans les premières secondes, en réalité le temps que npm récupère les paquets est assez long (près de 2 minutes…).

Et vous verrez aussi que je me plante bien souvent sur les chemins d’import ;)

Petit bilan

En toute honnêteté, j’ai perdu beaucoup de temps à lire des didactitiels eronnés ou trop vieux (datant de l’époque de Angular alpha). J’avais pris Angular 2 comme quelque chose de complexe, trop strict et franchement pas adapté. Mais en me penchant un peu, en arrêtant de copier-coller du code d’articles qui m’apportaient plus de questions que de réponses, j’ai compris rapidement que tout était bien plus simple que ça n’y parait.

Evitez de vous perdre dans des notions abstraites telles que les “observers” et allez-y “naturellement” pour commencer. Créez vous un composant simple, un truc qui affiche des valeurs contenues dans la classe. Ensuite, créez un formulaire et essayez de binder les valeurs, de réagir au “submit”. Rapidement les concepts s’imbriquent et la lumière apparait.

Angular 2 propose quelque chose de bien plus orienté “développement” que ne le faisait sa version 1. Le fait de passer à Typescript rend le projet bien plus structuré et largement moins compliqué à partager dans une équipe.

comments powered by Disqus