Angular 2

@EmmanuelDemey

Emmanuel DEMEY

Consultant & Formateur Web

Zenika Nord

       @EmmanuelDemey

Web / Domotique / Histoire / Biérologie

#kaiznday

Préparer votre application à Angular2

John Papa's styleguide

Autres Règles

  • 1 composant AngularJS par fichier
  • ECMAScript 2015
  • Syntaxe Component As 
  • bindToController dans l'API des directives
  • Component-First Approach
  • Utilise le nouveau Router
  • Création de service via la méthode service

Mise en pratique

eslint-plugin-angular

npm install --save-dev eslint eslint-plugin-angular
  • Implémente le guideline de John Papa
  • Rules :
    • Component-First Approach : ng_no_controller
    • controllerAs : ng_controller_as_*

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(...) { ... }       
    }
});

API des Directives

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

API des Directives

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

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

2-way data-binding

<input ng-model="firstName"
       ng-model-options="options">

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

2-way data-binding

<input ng-model="firstName">

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

Et ce n'est pas fini...

Mais aussi...

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

La solution... Angular 2

Attention !

Version Alpha

P(eu|as) de Doc

  • 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 Web Components

Custom Elements

Templates

Imports

Shadow DOM

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
//<my-app></my-app>
function MyAppComponent() {

}

MyAppComponent.annotations = [
    new angular.ComponentAnnotation({
        selector: 'my-app'
    }),
    new angular.ViewAnnotation({
        template: "<main>" + 
                       "<h1> This is my first Angular2 app</h1>" +
                  "</main>"
    })
];

Composant version ES5

import {Component, View} from 'angular2/angular2';

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

}

Composant version TypeScript

import {Component, View, bootstrap} from 'angular2/angular2';

@Component({selector: 'my-app'})
@View({
    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 [attr]="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

//<beerItem [beer]="'Maredsous'"></beerItem>
@Component({
  selector: 'beerItem',
  inputs: ['beer']
})
@View({
  template: `<section>
                <h2>{{beer}}</h2>
                <button>Je veux celle-ci !</button>
            </section>`
})
class BeerItem {
    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

//<beerItem [beer]="'Maredsous'" (selectBeer)="sendToBeerTap()"></beerItem>
@Component({
  selector: 'beerItem',
  inputs: ['beer'],
  outputs: ['selectBeer']
})
@View({
  template: `<section>
                <h2>{{beer<}}</h2>
                <button (click)="selectItem()">Je veux celle-ci !</button>
            </section>`
})
class BeerItem {
  beer: String;
  selectBeer: EventEmitter;
  
  selectItem() {
     this.selectBeer.next(this.beer);
  }
}
Attribute names must consist of one or more characters other than the space characters, U+0000 NULL, """, "'", ">", "/", "=", the control characters, and any characters that are not defined by Unicode.

Syntaxe valide ?

Syntaxe valide ?

Component Dependency

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

Component Dependency

import {Component, View, bootstrap, NgFor} from 'angular2/angular2';
import {BeerItem} from 'BeerItem';

@Component({ selector: 'my-app'})
@View({
    template: `<main class="mdl-layout__content">
                    <ul class="googleapp-card-list">
                        <li *ng-for="#beer of beers">
                            <beerItem [beer]="beer"></beerItem>
                        </li>
                    </ul>
                </main>`,
    directives: [NgFor, BeerItem]
})
class MyAppComponent {
    public beers: String[] = [];
    constructor() { ... }
}

Support des WebComponents

WebComponents

@Component({ })
@View({
  template: string,
  templateUrl: string,
  encapsulation?: ViewEncapsulation,
  directives?: List<any>,
  styles?: List<string>,
  styleUrls?: List<string>,
})
class BeerItem { }

WebComponents

//From modules/angular2/src/core/render/api.ts

/**
 * How the template and styles of a view should be encapsulated.
 */
export enum ViewEncapsulation {
  /**
   * Emulate scoping of styles by preprocessing the style rules
   * and adding additional attributes to elements. This is the default.
   */
  Emulated,
  /**
   * Uses the native mechanism of the renderer. For the DOM this means creating a 
   * ShadowRoot.
   */
  Native,
  /**
   * Don't scope the template nor the styles.
   */
  None
}

Injection de Dépendance

Injection de Dépendances

  • Code métier dans des services
  • Chaque Service est un singleton
  • Principe d'Hollywood
  • Multiples implémentations en NG1 !

Injection de Dépendances

app.service('TapService', function($http){
    
    this.getBeer = function(beerId){
        return $http.get('/api/i-am-thirsty/' + beerId);
    };
});

app.controller('AppCtrl', function(TapService){
    
    this.selectBeer = function(idBeer){
        return TapService.getBeer(idBeer);
    }
});

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 - toValue

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

bootstrap(MyAppComponent, [bind(String).toValue('Zenika Brewery')]);

Injecteur Principal - toClass

@Component({selector: 'my-app'})
@View({
    template: `<main>
                 <h1> Welcome to our {{breweryName}}</h1>
               </main>`
})
class MyAppComponent {
    constructor(private breweryService:BreweryService) { 
        this.breweryName = this.breweryService.getBreweryName();
    }
}

bootstrap(MyAppComponent, [bind(BreweryService).toClass(BreweryService)]);

Injecteur Principal - toClass

@Component({selector: 'my-app'})
@View({
    template: `<main>
                 <h1> Welcome to our {{breweryName}}</h1>
               </main>`
})
class MyAppComponent {
    constructor(private breweryService:BreweryService) { 
        this.breweryName = this.breweryService.getBreweryName();
    }
}

bootstrap(MyAppComponent, [BreweryService]);

Injecteur Principal - toFactory

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

bootstrap(MyAppComponent, 
        [bind(String)
            .toFactory((BreweryService) => {
                return BreweryService.getBreweryName();          
            }, [BreweryService])]);

Child Injector

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

bootstrap(MyAppComponent, [bind(String).toValue('Zenika Brewery')]);

Child Injector

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

Child Injector

@Component({
    selector: 'welcome-message'
})
@View({
    template: `<h1>Welcome to our {{breweryName}}</h1>`,
    bindings: [
        bind(String).toValue('Awesome Zenika Brewery')
    ]
})
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/angular2';

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

Pipes

import {Component, View} from 'angular2/angular2';
import {UpperCasePipe} from './UpperCasePipe.ts'

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

Pipes

import {Component, View} from 'angular2/angular2';
import {UpperCasePipe} from './UpperCasePipe.ts'

@Component({
  selector: 'widget1'
})
@View({ 
    template: ``,
    bindings: [UpperCasePipe] 
})
export class Widget1{
    constructor(public upperCasePipe:UpperCasePipe){
      this.upperCasePipe.transform('Un autre exemple...');
    }
}

@Component

@View

@Directive

@Animation

@Inject

@InjectLazy

@Optional

@Host

@Parent

@Pipe

@Property

@Event

@RouteConfig

@HostBinding

@HostEvent

@ContentChild

@ContentChildren

@ViewChild

@ViewChildren

@Component

@View

@Directive

@Animation

@Inject

@InjectLazy

@Optional

@Host

@Parent

@Pipe

@Property

@Event

@RouteConfig

@HostBinding

@HostEvent

@ContentChild

@ContentChildren

@ViewChild

@ViewChildren

Roadmap

ngUpgrade.js

Mixer AngularJS et Angular2

Démo

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

Questions ?

Kaiz'n Day

By Emmanuel Demey

Kaiz'n Day

  • 723
Loading comments...

More from Emmanuel Demey