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
- Création d'un module
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
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
- 2,067