Angular 2

@EmmanuelDemey

Emmanuel DEMEY

Consultant & Formateur Web

Zenika Lille

       @EmmanuelDemey

Web / Domotique / Biérologie

Préparer votre application à Angular2

John Papa's styleguide

Service AngularJS

  • Utiliser la méthode service
  • Similaire à l'implémentation Angular2
angular.module('MyApp')
    .service('BreweryService', function(){
        
        this.getBeers(){ ... }
    
    });

Utilisation de ngAnnotate

angular.module('MyApp')
    .controller('BreweryCtrl', 
        ['BreweryService', 
        function(BreweryService){
        
            BreweryService.getBeers();
        
        }]);
function BreweryCtrl(BreweryService){
    BreweryService.getBeers();
}
BreweryCtrl.$inject = ['BreweryService']

angular.module('MyApp')
    .controller('BreweryCtrl', BreweryCtrl);

Utilisation de ngAnnotate

  • Eviter les problèmes après Minification
  • Plus de duplication de code
  • Intégration Grunt/Gulp
angular.module('MyApp')
    .controller('BreweryCtrl', 
        function(BreweryService){
        
            BreweryService.getBeers();
        
        });

Component-First Approach

  • Se baser sur des composants simples et  réutilisables
  • Bannir l'utilisation des ng-controller
  • Architecture identique à Angular2
  • Eviter les contrôleurs et templates trop longs

controllerAs / bindToController

  • Eviter d'utiliser le scope 
    • dans les contrôleurs
    • dans les directives
  • Faciliter la hiérarchie des contrôleurs
angular.module('MyApp')
    .controller('BreweryCtrl', 
        function(){
        
            this.beers = [];
        
        });
<ul ng-controller="BreweryCtrl as bc">
    <li ng-repeat="beer in bc.beers">
        {{beer}}
    </li>
</ul>

ES2015 / TypeScript

  • Définition des composants AngularJS via des classes
  • Utilisation des modules ES2015
class BreweryCtrl {

    constructor() { ... }

    selectBeer() { ... }
}
export { BreweryCtrl }
import { BreweryCtrl} from './BreweryCtrl';

angular
    .module('app', [])
    .controller('BreweryCtrl', BreweryCtrl);

Autres Bonnes Pratiques

  • 1 composant AngularJS par fichier
  • Utilise le nouveau Router
  • Utilisation de l'API Component
  • Utilisation AngularJS > 1.5
  • ...

Mise en pratique

Les choses que nous n'aimons pas...

Architecture AngularJS

DI (provider, service, factory...)

MV*

MV*

MV*

Filtres

API des Directives

app.directive('MyDirective', function(){
    return  {       
       restrict: 'AE',
       require: '?^^ngModel',
       scope: { variable: '@' },  
       compile: function(...) {
           return {
               pre: function(...) { ... },
               post: function(...) { ... }
           }
       },
       link: function(...) { ... }       
    }
});

            
<input ng-model="firstName">

<p>
   {{firstName }}
</p>

2-way data-binding

Et ce n'est pas fini...

Mais aussi...

  • JavaScript
  • Hiérarchie des scopes
  • Pas de Server-Side Rendering
  • Gestion des événements (ngClick, ...)
  • Gestion des attributs HTML (ngSrc, ...)

La solution... Angular 2

  • Architecture
  • Composants
  • Injection de Dépendance
  • Pipes

Architecture Angular 2

<app></app>

menu

grid

gallery

DI
(classes ES6 ou TypeScript)
 

Pipes
(classes ES6 ou TypeScript)
 

La Famille JavaScript

ES5

ES2015

TypeScript

  • Classe
  • Modules
  • ...
  • Type Annotation
  • Meta Annotation
  • ...

Les composants

Composants Angular 2

  • Ressources de base en Angular 2
  • Tout est composant
  • Application représentée par un arbre de composants
  • Utilisation de métadonnées pour configurer un composant
import {Component} from '@angular/core';

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> This is my first Angular2 app</h1>
               </main>`
})
class MyAppComponent {

}

Composant version TypeScript

import {Component} from '@angular/core';
import {bootstrap} from '@angular/platform/browser';

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> This is my first Angular2 app</h1>
               </main>`
})
class MyAppComponent {

}
                
bootstrap(MyAppComponent);
            

Bootstrap de l'Application

Binding

Root Cpt

Child1 Cpt

Child2 Cpt

[property]="expression"

(event)="update()"

Property Binding

<input [property]="expression" />
  • Accès à toutes les propriétés des éléments HTML
  • Possibilité de définir de nouvelles propriétés
  • Compatibilité avec d'autres spécifications

Property Binding

<body>
    <h1>My First Angular2 app</h1>
</body>

Property Binding

<body>
    <h1 [textContent]="'My First Angular2 app'">
    </h1>
</body>

Property Binding

<body>
    <h1>{{'My First Angular2 app'}}
    </h1>
</body>

Property Binding

//<beer-item [beer]="'Maredsous'"></beer-item>
@Component({
  selector: 'beer-item',
  template: `<section>
                <h2>{{beer}}</h2>
                <button>Je veux celle-ci !</button>
            </section>`
})
export class BeerItem {
    @Input() beer: string;
}

Event Binding

<input (event)="expression" />
  • Accès à tous les événements des éléments HTML
  • Possibilité de définir de nouveaux événements

Event Bindings

//<beer-item [beer]="'Maredsous'" (selectBeer)="sendToBeerTap($event)"></beer-item>
@Component({
  selector: 'beer-item',
  template: `<section>
                <h2>{{beer}}</h2>
                <button (click)="selectItem()">Je veux celle-ci !</button>
            </section>`
})
export class BeerItem {
  @Input() beer: string;
  @Output() selectBeer: EventEmitter = new EventEmitter<string>();
  
  selectItem() {
     this.selectBeer.next(this.beer);
  }
}

Component Dependency

  • Nécessité d'importer les composants nécessaires à votre application
  • Propriété directives de @Component
  • Erreur si composants non utilisés

Component Dependency

import {BeerItem} from 'BeerItem';

@Component({ 
    selector: 'my-app',
    template: `<main class="mdl-layout__content">
                    <ul class="googleapp-card-list">
                        <li *ngFor="let beer of beers">
                            <beer-item [beer]="beer"></beer-item>
                        </li>
                    </ul>
                </main>`,
    directives: [BeerItem]
})
export class MyAppComponent {
    public beers: string[] = [];
    constructor() { ... }
}

Injection de Dépendance

DI version Angular2

  • 1 Injecteur principal + 1 Injecteur par composant
    • Hérite de l'injecteur parent
    • Possibilité de redéfinir le Service à injecter
  • Utilisation d'annotations en ES6 et des types en TypeScript
  • Services disponibles via le constructeur du composant

Injecteur Principal - toClass

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> Welcome to our {{breweryName}}</h1>
               </main>`
})
class MyAppComponent {
    
    breweryName:string;

    constructor(private breweryService:BreweryService) { 
        this.breweryName = this.breweryService.getBreweryName();
    }
}

bootstrap(MyAppComponent, [BreweryService]);

Injecteur Principal - toValue

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> Welcome to our {{breweryName}}</h1>
               </main>`
})
class MyAppComponent {
    constructor(public breweryName:string) { ... }
}

bootstrap(MyAppComponent, [{ provide: string, useValue: 'Zenika Brewery' }]);

Injecteur Principal - toClass

@Component({
    selector: 'my-app',
    template: `<main>
                 <h1> Welcome to our {{breweryName}}</h1>
               </main>`
})
class MyAppComponent {

    breweryName:string;

    constructor(private breweryService:BreweryServiceI) { 
        this.breweryName = this.breweryService.getBreweryName();
    }
}

bootstrap(MyAppComponent, [
        { provide: BreweryService, useClass: RivieraBreweryService }
]);

Child Injector

@Component({
    selector: 'my-app',
    template: `<main>
                 <welcome-message></welcome-message>
               </main>`,
    directives: [WelcomeMessage]
})
class MyAppComponent {
    constructor(public breweryName:string) { ... }
}

bootstrap(MyAppComponent, [{ provide: string, useValue: 'Zenika Brewery' }]);

Child Injector

@Component({
    selector: 'welcome-message',
    template: `<h1>Welcome to our {{breweryName}}</h1>`
})
export class WelcomeMessage {
    constructor(public breweryName:string) { ... }
}

Child Injector

@Component({
    selector: 'welcome-message',
    template: `<h1>Welcome to our {{breweryName}}</h1>`,
    providers: [
        [{ provide: string, useValue: 'Awesome Zenika Brewery' }]
    ]
})
export class WelcomeMessage {
    constructor(public breweryName:string){ ... }
}

Pipes

Petit Rappel


{{ collectionOfBeers | orderBy:'note' | limitTo:5 }}

Pipes

  • Identiques aux filtres d'AngularJS 1
  • Permet de manipuler une donnée
  • Utilisation d'une classe annotée @Pipe
  • Pipes disponibles dans le framework : 
    • upperCase, lowerCase, async, number, slice, json et date

Pipes

import {Pipe, PipeTransform} from 'angular2/core';

@Pipe({
  name: 'uppercase'
})
export class UpperCasePipe implements PipeTransform {
  transform(value: string, args: any[]) {
    
    return value.toUpperCase();
  
  }
}

Pipes

import {Component} from 'angular2/core';
import {UpperCasePipe} from './UpperCasePipe'

@Component({
  selector: 'widget1',
  template: `<div>{{'Démo utilisant les pipes' | uppercase}}</div>`,
  pipes: [UpperCasePipe]
})
export class Widget1{}

Démo

https://github.com/Gillespie59/angular2-migration-sample

Questions ?

Riviera Dev

By Emmanuel Demey

Riviera Dev

  • 2,507