Il était       une fois...

Un nouvel Angular

  • Une réécriture complète de ce bon vieil AngularJS

  • Pourquoi une réécriture complète ?

  • AngularJS ne suffisait-il donc pas ?

Besoin  d'évolution

  • AngularJS n’est pas parfait,
    concepts parfois
    difficiles à cerner

  • Le web a bien évolué depuis
    qu’AngularJS a été conçu

  • JavaScript a changé

  • De nouveaux
    frameworks/libs sont
    apparus avec de meilleures
    idées et implémentations

Le turfu

  • Angular a été conçu pour
    le web de demain :

    • avec ECMAScript 6

    • les Web Components

    • et le mobile en tête

      (performances)

#AngularToutCourt

  • On ne dit plus Angular 2

  • Angular 3, 4, 5, etc.

  • It's just Angular

  • AngularJS vs Angular

Technos

ES6

  • JavaScript (JS) est une implémentation de ECMAScript

  • Une nouvelle version est apparue :
    ECMASCRIPT 6, ES6, ou ECMASCRIPT 2015

  • ES6 est le nom le plus populaire

ES6

  • ES6 introduit deux nouveaux mot-clés
    pour la déclaration de variables : let et const

  • let/const permettent de déclarer
    des variables scopées

  • const ne produit des "vraies" constantes
    qu'avec des primitives

function getMovieTitle(movie) {
  if (movie.genre) {
    let title = movie.title + ':' + movie.genre;
  }
  // title is not accessible here, error
  console.log(title);
  return movie.title;
}
const MOVIES_PER_PAGE = 20;
MOVIES_PER_PAGE = 15; // Error

const MOVIE = {};
MOVIE.title = 'Superman'; // OK

ES6

  • ES6 introduit les classes en JavaScript

class Movie extends Media {
  constructor(title, genre = 'Action') {
    super(title);
    this.title = title;
    this.genre = genre;
  }

  static defaultPopularity() {
    return 10;
  }

  toString() {
    return this.title + '(' + this.genre + ')';
  }
}

let movie = new Movie('Titanic', 'Romance');
console.log(movie.toString()); // Titanic (Romance)

ES6

  • Ainsi que des vrais modules !

import defaultMember from 'module-name';
import * as name from 'module-name';
import { member } from 'module-name';
import { member as alias } from 'module-name';
import { member1 , member2 } from 'module-name';
import { member1 , member2 as alias2 , [...] } from 'module-name';
import defaultMember, { member [ , [...] ] } from 'module-name';
import defaultMember, * as name from 'module-name';
import 'module-name';

export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const
export expression;
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;

ES6

  • Nouvelle syntaxe de fonctions utilisant
    l’opérateur fat arrow :
    =>

  • Des template strings multilignes : backtick







     

  • Et bien plus encore : http://es6-features.org

// ES5
getUser(login)
 .then(function (user) {
   return getRights(user); // getRights returns a promise
 })
 .then(function (rights) {
   return console.log('User rights: ' + rights.join(',') + '...' );
 });

// ES6
getUser(login)
 .then(user => getRights(user))
 .then(rights => console.log(`User rights: ${rights.join(',')}...`))

TypeScript

  • Du typage dans JavaScript !

  • Des decorators

  • Sky is the limit...

interface MyFunction<T> {
  (param1:T, param2?:string):void
}

enum MyEnum {
  Trololo,
  Trilili,
  Tralala = 3
}

const myFunction:MyFunction<MyEnum> = function (param1, param2) {
  console.log(param1, param2);
}

myFunction(MyEnum.Trololo); // 0, undefined
myFunction(MyEnum.Tralala, 'three'); // 3, 'three'
myFunction('hello'); // type error

Installation
Configuration

AngularJS

  • Installation

     
  • Création d'un module

     
  • Bootstrap

     
  • Et c'est globalement tout !
bower install angular#1.6.4 angular-route#1.6.4 --save
angular.module('app-module', []);
<body ng-app="app-module">...</body>

Angular

  • TypeScript
    • Mise en place
    • tsconfig.json







       
    • Compilation
npm install -g typescript && tsc --init
{
  "compilerOptions": {
    "target": "es5",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "sourceMap": true,
    "module": "commonjs",
    "noImplicitAny": false,
    "lib": ["es2016", "dom"],
    "outDir": "dist",
    "rootDir":"src"
  }
}
tsc --watch

Angular

  • Framework
    • Mise en place
    • package.json






       
    • Et le module loader/packager de votre choix
npm --init && npm install <PLEIN DE TRUCS ICI>
"dependencies": {
   "@angular/common": "4.0.2",
   "@angular/compiler": "4.0.2",
   "@angular/core": "4.0.2",
   "@angular/platform-browser": "4.0.2",
   "@angular/platform-browser-dynamic": "4.0.2",
   "@angular/http": "4.0.2",
   "@angular/forms": "4.0.2",
   "reflect-metadata": "0.1.10",
   "rxjs": "5.3.0",
   "zone.js": "0.8.5"
 }

Angular

  • App
    • Création d'un module






       
    • Bootstrap
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';

@NgModule({
  imports: [BrowserModule, HttpModule, FormsModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Components

AngularJS

  • Deux façons de définir un composant
    • Directive à l'origine
    • Component depuis la version 1.5
function IntervalComponent($interval) {
  var ctrl = this;
  var interval = null;

  ctrl.$onInit = function () {
    interval = $interval(ctrl.onUpdate, ctrl.delay)
  };

  ctrl.$onDestroy = function () {
    $interval.cancel(interval)
  };
}

angular.module('app-module').component('intervalComponent', {
  controller: ['$interval', IntervalComponent],
  bindings: { delay: '=', onUpdate: '&' },
  templateUrl: 'interval-component.html'
});

Angular

  • Utilisation du decorator @Component










     
  • Ne pas oublier de le rattacher au module
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'interval-component',
  templateUrl: 'interval-component.html'
})
export class IntervalComponent implements OnInit, OnDestroy {
  private interval:number = null;

  ngOnInit():void {
    this.interval = window.setInterval(() => this.onUpdate.emit(), this.delay);
  }

  ngOnDestroy():void {
    window.clearInterval(this.interval);
  }

  @Input() delay:number = null;
  @Output() onUpdate:EventEmitter<void> = new EventEmitter();
}

AngularJS

  • {{}} pour l’interpolation

  • Une grande liste de directives fournies par le framework à connaître (comme ng-class, ng-if, ng-styles, ng-switch, ng-click,
    ng-submit, etc.)

Angular

  • {{}} pour l’interpolation

  • ? pour le "fail safe"

  • [] pour le binding
    de propriété

  • () pour le binding d’événement

  • # pour la déclaration
    de variable locale

  • * pour les directives structurelles

Angular

<p>{{ user?.name }}</p>

<div [innerHTML]="unsafeProperty" [class.toto]="shouldAddTotoClass()"></div>

<button type="button" (click)="youClickedMe($event)">Click me</button>

<input type="text" #myInput>
<button type="button" (click)="myInput.focus()">Click me too</button>

<interval-component [delay]="2000" (onUpdate)="intervalUpdated()"></interval-component>

<ul *ngIf="movies.length">
   <li *ngFor="let movie of movies; let i=index">{{ i }}-{{ movie }}</li>
</ul>

<ng-container [ngSwitch]="messageCount">
 <p *ngSwitchCase="0">You have no message</p>
 <p *ngSwitchCase="1">You have a message</p>
 <p *ngSwitchDefault>You have some messages</p>
</ng-container>

Directives

AngularJS

  • Pour ajouter du comportement à un élément
    • On change la couleur à l’initialisation
    • On reset au survol de la souris
angular.module('app-module').directive('textColor', function () {
  return {
    restrict: 'A',
    scope: { textColor: '@' },
    link: function (scope, element) {
      element.css('color', scope.textColor);
      element.on('mouseenter', function () {
        element.css('color', null);
      })
    }
  };
});

Angular

  • Utilisation du decorator @Directive








     
  • Ne pas oublier de la rattacher au module
import { Directive, Input, HostBinding, HostListener } from '@angular/core';

@Directive({
  selector: '[text-color]'
})
export class TextColorDirective {
  @Input('text-color')
  @HostBinding('style.color')
  color:string = null;

  @HostListener('mouseenter')
  resetColor() {
    this.color = null;
  }
}

Services

AngularJS

  • Service pour encapsuler de la logique métier
    • $http pour les appels au backend
    • On traite généralement avec des Promise ($q)
function ItemsService($http) {
  var service = this;
  
  function toData(response) {
    return response.data;
  }

  service.fetchItems = function () {
    var config = { params: { limit: 10 } };
    return $http.get('api/items', config).then(toData);
  };
}

angular.module('app-module').service('itemsService', ItemsService);

AngularJS

  • Le service HTTP d’AngularJS
    retourne des Promise
  • Celles-ci sont pratiques mais ne peuvent être annulées, seulement pending, resolved ou rejected
  • Potentiellement des soucis pour gérer l’annulation, notamment
    dans le cadre du router
  • Imaginons que l’on change d’écran alors qu’une requête est en
    cours d’exécution…

Angular

  • Pas de decorator spécifique, surprise !
    • Sauf peut être @Injectable...









 

  • Ne pas oublier de le rattacher au module
import { Injectable } from '@angular/core';
import { Http, Response, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Observable';
// Also add map, catch and throw for Observable

@Injectable()
export class ItemsService {
  constructor(private http:Http) {}

  fetchItems():Observable<any[]> {
    let params = new URLSearchParams();
    params.set('limit', '10');

    return this.http.get('api/items', { params })
      .map((response:Response) => response.json())
      .catch(error => Observable.throw(error));
  }
}

Angular

  • Le service HTTP d’Angular retourne des Observable

  • Il s’appuie sur RxJS

  • Les Observable sont des flux, ils ont donc une dimension temporelle et peuvent
    être terminés

  • On doit également souscrire aux flux pour déclencher la récupération des données

  • On peut arrêter la
    souscription à tout moment

Angular

  • Comme les Promise, les Observable peuvent être combinés, mais aussi transformés et chainés

  • La composition est donc très facile

  • Des Observable peuvent être créés
    à partir de Promise et être
    convertis en Promise

  • Il n’y a donc que des
    avantages à s’en servir !

  • Sauf peut être importer (poids)
    et apprendre (temps) RxJS

Angular

  • Exemple d’utilisation
    d’RxJS : live search

  • Ne pas envoyer de requête
    tant que l’utilisateur est
    en train d’entrer des
    caractères dans le champ

  • Ne pas envoyer de requête
    si la query ne change pas

  • Gérer les retours
    asynchrones dans l’ordre

  • Tout ceci est relativement
    compliqué sans RxJS

Angular

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { SearchService } from './search.service';

@Component({
  selector: 'search-cmp',
  template: `
    <h1>Search</h1>
    <input #term (keyup)="search(term.value)">
    <ul><li *ngFor="let result of results | async">{{ result }}</li></ul>
  `
})
export class SearchComponent {
  private searchStream = new Subject<string>();
  results:Observable<string[]>;

  constructor(private searchService:SearchService) {
    this.items = this.searchStream
      .debounceTime(300).distinctUntilChanged()
      .switchMap((query:string) => this.searchService.search(query));
  }
  
  search(query:string) { this.searchStream.next(query); }
}

Pipes

AngularJS

  • currency
  • date
  • json
  • uppercase
  • lowercase
  • number
  • limitTo
  • filter
  • orderBy

Angular

  • CurrencyPipe
  • DatePipe
  • JsonPipe
  • UpperCasePipe
  • LowerCasePipe
  • DecimalPipe
  • SlicePipe
  • I18nPluralPipe
  • I18nSelectPipe
  • PercentPipe
  • TitleCasePipe
  • AsyncPipe

Angular

  • DecimalPipe
<p>{{ 12345 }}</p>
<!-- will display '12345' -->

<p>{{ 12345 | number }}</p>
<!-- will display '12,345' -->

<p>{{ 12345 | number:'6.' }}</p>
<!-- will display '012,345' -->

<p>{{ 12345 | number:'.2' }}</p>
<!-- will display '12,345.00' -->

<p>{{ 12345.13 | number:'.1-1' }}</p>
<!-- will display '12,345.1' -->

<p>{{ 12345.16 | number:'.1-1' }}</p>
<!-- will display '12,345.2' -->
  • CustomPipe
import {
  Pipe,
  PipeTransform
} from '@angular/core';

@Pipe({ name: 'rating' })
export class CustomPipe
  implements PipeTransform {

  transform(value, args) {
    let stars: string = '';
    for (let i = 0; i < value; i++) {
      stars += '☆';
    }
    return stars;
  }
}
  • Ne pas oublier de rattacher CustomPipe au module

Angular

  • Angular introduit un pipe nommé async

  • Ce pipe fonctionne aussi bien sur
    des Promise que des Observable

  • Il souscrit aux changements et marque automatiquement le composant
    comme modifié

  • Le composant sera ensuite « re-rendu »

  • async gère aussi l’arrêt de la souscription

<ul><li *ngFor="let result of results | async">{{ result }}</li></ul>

Forms

AngularJS et Angular

  • Tous deux permettent de :
    • valider les saisies de l’utilisateur
    • avoir des champs obligatoires ou non,
      ou qui dépendent d’un autre champ
    • afficher les erreurs correspondantes
    • réagir sur les changements
      de certains champs
    • modifier facilement les
      styles des champs

Angular

  • Propose deux façons d’écrire les formulaires :

    • formulaire piloté par le template

    • formulaire piloté par le modèle (usage avancé)

  • Propose trois façons d'utiliser
    la directive ngModel



     

  • Le two-way binding est déconseillé

<input type="text" name="userName" ngModel>

<input type="text" name="userName" [ngModel]="user.name">

<input type="text" name="userName" [(ngModel)]="user.name">
<input type="text" name="userName" [ngModel]="user.name" (ngModelChange)="user.name = $event">

Angular

<style>
  input.ng-invalid {
    border-left: 5px red solid;
  }
  input.ng-valid {
    border-left: 5px green solid;
  }
</style>
<h2>Sign up</h2>
<form (ngSubmit)="register(userForm.value)" #userForm="ngForm">
  <div>
    Username:
    <input name="username" ngModel required maxlength="10" #username="ngModel">
    <p *ngIf="username.control.dirty && !username.control.valid">Invalid username</p>
  </div>
  <div>
    Password:
    <input type="password" name="password" ngModel required minlength="6" #password="ngModel">
    <p *ngIf="password.control.dirty && !password.control.valid">Invalid password</p>
  </div>
  <div>
    Phone:
    <input name="phone" ngModel required pattern="06[0-9]{8}" #phone="ngModel">
    <p *ngIf="phone.control.dirty && !phone.control.valid">Invalid phone number</p>
  </div>
  <button type="submit" [disabled]="!userForm.valid">Register</button>
</form>

Router

AngularJS

  • Router de base
    trop simple
  • Ne permet pas les routes imbriquées
  • Préférence pour
    le UI Router (tiers)

Angular

  • Réécrit plusieurs fois
  • Emprunte des idées
    au UI Router
  • Configuration puissante
  • Associe un chemin
    à un composant
  • Concept de Guard
  • Lazy loading possible
  • Mode HTML5 par défaut

Performances
Optimisation

AngularJS

  • AngularJS fait du dirty checking

  • Souvent pointé du doigt pour
    des soucis de performance

  • Cycles de digestion
    potentiellement lourds

  • Solution « temporaire » :
    one way binding (::)

  • Nécessité d’appeler
    $scope.$apply dans les
    callbacks de libs tierces

Le changement...

  • Dans tous les cas, les changements
    dans le modèle sont simples à tracer

  • Soit une action utilisateur :
    event (click, submit, etc.)

  • Soit le retour d’un appel backend :
    XHR, Sockets

  • Soit le passage d’un timer :
    setTimeout, setInterval

  • Changements toujours asynchrones

Angular

  • La détection de changements entièrement revue

  • La circulation des données aussi (prop + event)

  • Angular s’appuie sur Zone.js

  • « Portage » des zones de Dart

  • Les zones interceptent les appels asynchrones

  • Et offrent des hooks pour réagir en conséquence

  • La détection est donc transparente, rapide
    et fonctionne sans $apply (librairies tierces)

Conclusion

Angular

  • Des concepts qui
    ​« n’existent plus »
    • Les phases config/run
    • La définition de value/constant
    • Les controllers, le scope
    • Donc également $watch, $broadcast, $emit
    • Les intercepteurs HTTP
    • ...

Angular

  • Module loader obligatoire
    • SystemJS ou Webpack
      par exemple
    • Webpack fait aussi le packaging (Tree Shaking ?)
    • Prendre en considération
      AoT vs JiT
    • Mais aussi tout éventuel lazy loading (par route)

Angular

  • Plusieurs gros changements…

  • TypeScript + Polyfills

  • Configuration et démarrage
    plus complexe qu’AngularJS

  • Beaucoup d’outillage
    (transpilation, packaging, etc.)

  • Framework complet
    !== Library (cf React)

  • Mais code globalement
    plaisant à écrire

Merci aussi à Youness Mourad

Merci !

Sebastien
Pittion

Savinien
Richter

Question time!

AngularJS vs Angular

By fingerproof

AngularJS vs Angular

  • 1,981