Angular 2 - Part I - A new hope (or not) ?
Ca y est, comme pour le boujolais nouveau, nous avons la première version utilisable d'Angular 2 (même si elle est déconseillé de l'utiliser en production pour l'instant). Même si cette nouvelle n'est pas récente, l'objectif de cet article est d'essayer de voir les changements (brutaux ?) que nous amène cette nouvelle version.
Car mon sentiment à l'heure actuelle, est que nous courons une scission, tout comme nous l'avons eu avec Play! 1.x et Play! 2.x, où nous aurons deux communautés distinctes, mais surtout deux frameworks qui évolueront en parallèle.
Tout d'abord, les promesses d'Angular 2 (et elles sont légions):
- Bien plus rapide
- Réduire la taille des dépendances pour Angular
- Avoir une commande CLI Angular (via NodeJs) afin d'aider le développement à générer son application Angular 2
- Support de base des animations, du I18n et du L10n
- Plus de support pour le côté Web mobile
- Amélioration des test unitaires mais aussi des tests d'intégrations
- Amélioration de composants de bases comme le Router
- Style architectural plus dans l'air du temps et adapté à nos besoins (avec du coup, des nouveaux composants, de nouvelles approches)
- Orientation vers ES6 (même si développez une application en ES5 sera toujours possible)
- Intégration avec TypeScript (Babel est en cours de finalisation)
- Server-side rendering
- Un set d'outils pour nos différents IDE
- Des composants Angular 2 se basant sur Material Design
Bref, comme nous pouvons le voir, un tas de fonctionnalités afin de nous aider à construire des applications poussées et performantes.
Ng-upgrade / Ng-forward
Les équipes Angular nous fournissent déjà deux outils à notre disposition: Ng-Upgrade / Ng-Forward.
L'objectif ? Tout simplement pour le premier pouvoir utiliser des nouveautés d'Angular 2 au sein d'une application déjà construite en Angular 1. Permettant ainsi une migration en "douceur).
Mais nous verrons tout à l'heure comment construire une application se basant sur Angular 2. Et nous verrons que Ng-upgrade / Ng-forward n'est intéressant que pour de petits projets.
En somme, Ng-forward (principalement) va fournir des services pouvant être utilisés via Angular 1.x. Mais le lot de services sera limité.
Pour un petit exemple:
Initialisation d'une application Angular 2
Pour respecter la nouvelle approche, nous allons écrire notre application en ES6. Et pour nous faciliter la tâche, nous allons utiliser JSPM. Je l'avais déjà évoqué dans un précédent article.
Même si j'ai une préférence pour Babel, TypeScript est recommandé . Vu que les équipes Google ont travaillés conjointement avec les équipes Microsoft pour améliorer TypeScript afin de pouvoir avoir accès à certaines fonctionnalités d'ES6 et d'ES7 pour nous aider à écrire des applications Angular 2. Je pense notamment aux annotations / decorators ES7. De plus, il est clairement dit dans la liste des améliorations que Babel (ou tout du moins le pure eS6 / ES7) sera supporté un peu plus tard.
Pour commencer, créons un répertoire "angular2-superheroes" (le but étant de créer une petite application pour afficher des super-héros.
Initialisons notre projet avec NPM:
> npm init
Puis avec JSPM:
> jspm init
N'oublions pas comme dans le précédent article de modifier dans "config.js" la valeur "baseURL" à vide pour faire des chargements de modules en relatifs:
Et nous aurons une architecture de projet de la sorte (ou le répertoire "app" contiendra toute notre logique):
Une application Angular 2.0 de base
Commençons par déclarer une application Angular 2.0 de base. Pour se faire, nous tout d'abord télécharger les dépendances vers Angular 2.0. Du coup, nous allons demander à JSPM de le faire pour nous (et nous allons voir que cela va demander un certain nombre de sous-dépendances):
> jspm install angular2
A noter qu'il faudra importer également quelques petits modules à côté:
> jspm install core-js css reflect-metadata npm:@reactivex/rxjs
Maintenant, nous allons définir notre page "index.html" comme suit:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Angular 2.0 application for superheroes (with ES6 and JSPM)</title> <!-- Import styles --> <link rel="stylesheet" href="styles/index.css" /> <!-- Base imports --> <script type="text/javascript" charset="utf-8" src="jspm_packages/system.js"></script> <script type="text/javascript" charset="utf-8" src="config.js"></script> <!-- Import the application --> <script type="text/javascript" charset="utf-8" async="async"> System .import('./app/index') .then(function () { console.info('Application loaded'); }) .catch(function (ex) { console.error(ex.stack || ex); }); </script> </head> <body> <superheroes-app></superheroes-app> </body> </html>
Que pouvons-nous voir ? Nous importons "system.js" et "config.js" pour que JSPM puisse charger les modules que nous pourrions avoir besoin.
Nous avons également une balise script qui va charger le fichier "index.js" du répertoire "app", qui contient le "starter" Angular2.
Et enfin, nous avons définie une balise se nommant "superheroes-app" qui contiendra l'ensemble de notre application.
Voyons ce que nous avons dans notre fichier "app/index.js":
/** * Entry point for the Angular 2.0 application * * @module app/index * @version 1.0 * @since 1.0 */ // Import 3rd-party dependencies. import 'core-js/es6'; import 'reflect-metadata'; // Import Angular 2 and application modules import { Component, View } from 'angular2/core'; import { bootstrap } from 'angular2/bootstrap'; @Component({ 'selector': 'superheroes-app' }) // Name of the tag @View({ 'template': ` <h1>Superheroes list</h1> ` }) class Application { // Nothing for the moment } // Startup the Angular 2 application bootstrap(Application);
Là, que constatons-nous ? Tout d'abord qu'il nous faut charger en mémoire deux modules externes pour Angular2 fonctionne, à savoir le module "core-js/es6" et "reflect-metadata". Ce dernier sert lorsque nous utilisons les decorators / annotations.
Enfin, nous importons des éléments d'Angular2 ! Comme nous le constatons, nous allons définir un composant de base. Ce dernier sera représenté par le tag "superheroes-app" (comme utilisé dans notre fichier "index.html") via l'annotation @Component et nous fournissons son template via l'annotation @View. Rassurez, vous pouvez externaliser le template dans un fichier HTML en précisant la propriété url au lieu de template.
Et pour finir, l'appel à la fonction "bootstrap" permet de démarrer concrêtement l'application (en précisant bien quel composant Angular2 représente notre application).
Ce qui nous donne (les couleurs flashies, c'est parce que nous utilisons des couleurs de super-héros :) ):
Définissons notre premier service
Maintenant, nous allons écrire notre premier service, qui va nous retourner des informations sur la liste de super-héros.
Pour commencer, écrivons le modèle d'un super-héros dans le fichier "app/models/superhero.js":
/** * Superhero model * * @module app/model/superhero * @exports SuperHero * @version 1.0 * @since 1.0 */ /** * @class SuperHero */ export default class SuperHero { /** * @constructor * @param {string} [uuid='1'] * @param {string} [firstName='Bruce'] * @param {string} [lastName='Wayne'] * @param {number} [age=36] */ constructor (uuid = '1', firstName = 'Bruce', lastName = 'Wayne', age = 38) { /** * @property {string} uuid */ this.uuid = uuid; /** * @property {string} firstName */ this.firstName = firstName; /** * @property {string} lastName */ this.lastName = lastName; /** * @property {number} age */ this.age = age; } /** * Return the string representation of a user * @returns {string} */ toString() { return `(${this.uuid}) ${this.firstName} ${this.lastName}: age ${this.age}`; } }
Ensuite, écrivons le service associé dans le fichier "app/services/superheroes.js". A noter ici que nous ne déclarons pas sur notre classe des méthodes statiques. En effet, Angular2 utilise la même approche que pour "angular.service" (et non "angular.factory") à savoir un object qui sera instancié lors du premier import (mais instancié qu'une seule fois):
/** * Superheroes service * * @module app/services/superheroes * @exports SuperHeroesService * @version 1.0 * @since 1.0 */ import SuperHero from '../models/superhero'; /** * List of users * * @constant * @private * @type {SuperHero[]} */ const SUPERHEROES = [ new SuperHero(), new SuperHero('2', 'Peter', 'Parker', 22), new SuperHero('3', 'Clark', 'Kent', 32), new SuperHero('4', 'Tony', 'Stark') ]; /** * @class SuperHeroesService */ export default class SuperHeroesService { /** * Get all superheroes ! * * @method * @returns {SuperHero[]} */ fetch() { return SUPERHEROES; } /** * Count the number of @{SuperHero} * * @method * @returns {number} */ size() { return SUPERHEROES.length; } }
Maintenant, étendons notre composant "Application". Nous allons injecter le service. Pour cela:
- Nous importons le decorator @Inject
- Nous modifions le template pour utiliser la méthode "size"
- Nous injectons le service via le constructeur
- un peu comme dans Spring au fond, ce qui sera plus simple pour faire des tests
- Et notamment les mocks
- Mais surtout, lorsque nous appelons la méthode "bootstrap", nous indiquons qu'il y a un service qui pourra être injecté
// Import needed services import SuperHeroesService from './services/superheroes'; @Component({ 'selector': 'superheroes-app' }) // Name of the tag @View({ 'template': ` <h1>Superheroes list ({{ superHeroesService.size() }})</h1> ` }) class Application { // Variables /** @property {SuperHeroesService} */ superHeroesService; /** * @constructor * @param {SuperHeroesService} superHeroesService */ constructor(@Inject(SuperHeroesService) superHeroesService) { this.superHeroesService = superHeroesService; } } // Startup the Angular 2 application bootstrap(Application, [SuperHeroesService]);
Ce qui nous donne bien:
Notre première "vraie" directive
Là, nous allons faire en sorte d'afficher notre liste de super-héros. Pour ce faire, nous allons de nouveau utiliser les annotations @Component et @View dans le fichier "app/components/superhero-entry.js". Le composant s'appellera alors "SuperHeroEntry".
Afin de lui passer un objet de type "SuperHero", nous allons utiliser le decorator @Input que nous placerons sur une propriété de la classe du composant. Nous pouvons écrire de la sorte:
/** * Component to display as entry a @{SuperHero} * * @module app/components/superhero-entry * @export SuperHeroEntryComponent * @version 1.0 * @since 1.0 */ // Import Angular 2 and application modules import { Component, View, Input } from 'angular2/core'; @Component({ 'selector': 'superhero-entry' }) @View({ 'directives': [], 'template': ` {{ superhero.toString() }} ` }) export default class SuperHeroEntry { // Passed values /** @property {SuperHero} */ @Input('superhero') superhero;
}
Comme nous pouvons le voir, le composant a une propriété "superhero" dans lequel nous pourrons passer en paramètre un super-héros.
Maintenant, utilisons le dans le composant d'application. Pour cela nous allons devoir:
- Importer les directives de bases que nous voulons utiliser (comme le NgFor)
- Pour information, nous aurions pu utiliser CORE_DIRECTIVES qui permet de récupérer l'ensemble des directives de bases d'Angular2.
- Il est à noter aussi que dans ce cas, le nom de la variable qui référence la directive / composant est écrit en Pascal Case. Et du coup, le nom de la variable servira pour le tag
- Utile en cas de conflit de nom
- Du coup, la propriété "selector" sert surtout pour indiquer un fallback
- Importer le composant SuperHeroEntry
- Déclarer sur le template que ces deux directives seront présents
- Modifier le template
// Import needed Angular 2 components import { NgFor } from 'angular2/common'; import SuperHeroEntry from './components/superhero-entry'; @Component({ 'selector': 'superheroes-app' }) // Name of the tag @View({ 'directives': [NgFor, SuperHeroEntry], 'template': ` <h1>Superheroes list ({{ superHeroesService.size() }})</h1> <br /> <ul> <li *ngFor="#superhero of superHeroesService.fetch()"> <superhero-entry [superhero]="superhero"></superhero-entry> </li> </ul> ` }) class Application { // Variables /** @property {SuperHeroesService} */ superHeroesService; /** * @constructor * @param {SuperHeroesService} superHeroesService */ constructor(@Inject(SuperHeroesService) superHeroesService) { this.superHeroesService = superHeroesService; } }
Que constatons-nous ? Tout d'abord, nous devons préciser avant le ngFor un astérix. La raison a cela serait que la directive / composant va être autoriser à exposer dans le contexte du template une nouvelle propriété. En l'occurence, la propriété "superhero" qui est déclaré via #superhero.
Ensuite, nous utilisons notre composant SuperHeroEntry en déclarant entre crochet le nom de la propriété du composant sur lequel nous voulons passer le super-héros. Et le tour est joué !
Conclusion
Comme nous pouvons le voir, la façon d'écrire une application Angular 2 est vraiment différente d'une application Angular 1.x. Néanmoins, nous avons à la fois plus de contrôle sur la qualité du code, mais aussi plus de facilité de lecture et d'optimisation.
Nous verrons dans un second temps comment étendre la directive, mettre en place un routeur, formulaire et les tests.
Commentaires
Enregistrer un commentaire