Moteur de rendu Angular 6 et Ivy

image Bonjour, chers collègues. Nous envisageons de mettre à jour le livre de Jacob Fine et Anton Moiseev " Angular et TypeScript. Création de site Web pour les professionnels ". Une nouvelle édition sortira cet automne et comprend du matériel sur Angular 5 et 6.

Au début, nous pensions publier du matériel sur le moteur Ivy, qui est probablement l'innovation la plus intéressante dans Angular 6, mais nous nous sommes ensuite arrêtés sur une publication plus détaillée de Cedric Exbright (l'original a été publié en mai).

Dans Angular 6, il y avait beaucoup d'innovations sérieuses, d'ailleurs, la plus importante d'entre elles, vous ne pouvez pas nommer de fonctionnalités: c'est Ivy, un nouveau moteur de rendu. Le moteur étant encore expérimental, nous en parlerons à la fin de cet article, et commencerons par d'autres nouvelles fonctionnalités et changements révolutionnaires.

Fournisseurs pouvant être bousculés

Il existe maintenant une nouvelle méthode recommandée pour enregistrer le fournisseur directement dans le décorateur @Injectable() , en utilisant le nouvel attribut @Injectable() . Il prend 'root' comme valeur de n'importe quel module de votre application. Lors de l'utilisation de 'root' objet implémenté sera enregistré dans l'application en tant que solitaire, et vous n'aurez pas besoin de l'ajouter aux fournisseurs dans le module root. De même, lors de l'utilisation de providedIn: UsersModule objet implémenté est enregistré en tant UsersModule fournisseur UsersModule et n'est pas ajouté aux fournisseurs de modules.

 @Injectable({ providedIn: 'root' }) export class UserService { } 

Une telle nouvelle méthode a été introduite pour mieux supprimer le code non fonctionnel dans l'application (arborescence). À l'heure actuelle, la situation est telle que le service ajouté aux fournisseurs du module se retrouvera dans l'ensemble final, même s'il n'est pas utilisé dans l'application - et le permettre est un peu triste. Si vous utilisez le chargement paresseux, vous pouvez tomber dans plusieurs pièges à la fois ou vous retrouver dans une situation où le service sera entré dans le mauvais ensemble.

Une telle situation dans les applications est peu susceptible de se produire souvent (si vous écrivez un service, puis l'utilisez), mais les modules tiers offrent parfois des services dont nous n'avons pas besoin - en conséquence, nous avons tout un tas de JavaScript inutile.

Cette fonctionnalité sera donc particulièrement utile pour les développeurs de bibliothèques, mais il est maintenant recommandé d'enregistrer les objets implémentés de cette manière - cela s'applique également aux développeurs d'applications. La nouvelle CLI utilise désormais même l'échafaudage providedIn: 'root' par défaut lors de l'utilisation des services.

Dans le même esprit, vous pouvez désormais déclarer un InjectionToken , l'enregistrer directement auprès de providedIn et ajouter une factory ici:

 export const baseUrl = new InjectionToken<string>('baseUrl', { providedIn: 'root', factory: () => 'http://localhost:8080/' }); 

Remarque: cela simplifie également les tests unitaires. Aux fins de ces tests, ils sont utilisés pour enregistrer le service auprès des fournisseurs du module de test. Voici ce que nous avons fait auparavant:

 beforeEach(() => TestBed.configureTestingModule({ providers: [UserService] })); 

Maintenant, si le UserService utilise providedIn: 'root' :

 beforeEach(() => TestBed.configureTestingModule({})); 

Ne vous inquiétez pas: tous les services enregistrés auprès de providedIn ne sont pas chargés dans le test, mais sont instanciés paresseusement, uniquement dans les cas où ils sont vraiment nécessaires.

Rxjs 6

Angular 6 utilise désormais RxJS 6 en interne, vous devez donc mettre à jour l'application dans cet esprit.

Et ... RxJS 6 change l'approche de l'importation!

Dans RxJS 5, vous pouvez écrire:

 import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/map'; const squares$: Observable<number> = Observable.of(1, 2) .map(n => n * n); 

Dans RxJS 5.5, des instructions canalisables sont apparues:

 import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { map } from 'rxjs/operators'; const squares$: Observable<number> = of(1, 2).pipe( map(n => n * n) ); 

Et dans RxJS 6.0, les importations ont changé:

 import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; const squares$: Observable<number> = of(1, 2).pipe( map(n => n * n) ); 

Ainsi, un jour, vous devrez modifier les importations dans l'ensemble de l'application. J'écris «une fois», et non «pour l'instant», car la bibliothèque compatible RxJS est sortie dans RxJS, ce qui permet de télécharger RxJS vers la version 6.0, même si les anciennes versions sont toujours utilisées dans l'ensemble de votre application ou dans l'une des bibliothèques utilisées syntaxe.

L'équipe Angular a écrit un document entier sur ce sujet, et il est absolument nécessaire de le lire avant de migrer vers Angular 6.0.

Remarque: voici un ensemble de règles tslint très cool appelé rxjs-tslint . Il ne contient que 4 règles, et si vous les ajoutez au projet, le système migre automatiquement toutes vos importations et votre code RxJS, et cela se fait par le plus simple tslint --fix ! Après tout, si vous ne savez toujours pas, dans tslint il y a une option de fix qui corrige automatiquement toutes les erreurs qu'il trouve! Il peut être utilisé encore plus simplement: installez globalement rxjs-tslint et exécutez rxjs-5-to-6-migrate -p src/tsconfig.app.json . J'ai essayé rxjs-tslint dans l'un de nos projets et cela a très bien fonctionné (exécutez-le au moins deux fois pour rxjs-tslint également toutes les importations). Consultez le README de ce projet pour plus de détails: github.com/ReactiveX/rxjs-tslint .

Si vous souhaitez en savoir plus sur RxJS 6.0, je recommande le prochain rapport de Ben Lesch sur ng-conf.

i18n

La perspective la plus importante associée à i18n est la possibilité de créer «i18n à l'exécution», sans avoir à créer l'application séparément pour chaque point local. Cette fonctionnalité n'est pas encore disponible (il n'y a que des prototypes), et le moteur Ivy sera nécessaire pour son fonctionnement (en savoir plus ci-dessous).

Un autre changement lié à i18n a déjà eu lieu et est disponible. Le canal des devises est optimisé de la manière la plus efficace: il arrondit désormais toutes les devises non pas à 2 chiffres, comme auparavant, mais au nombre de chiffres souhaité (par exemple, à 3 dans le cas du dinar bahreïni ou à 0 pour le peso chilien).

Si nécessaire, cette valeur peut être récupérée par programme à l'aide de la nouvelle fonction i18n getNumberOfCurrencyDigits .

D'autres fonctions de formatage pratiques, telles que formatCurrency , formatCurrency , formatPercent et formatNumber également apparues dans le formatNumber formatPercent .

Assez commodément, si vous souhaitez appliquer les mêmes transformations que celles effectuées dans les canaux, mais faites-le à partir du code TypeScript.

Des animations

Dans Angular 6.0, les animations sont déjà possibles sans polyfill web-animations-js , sauf si vous utilisez AnimationBuilder . Votre application peut gagner de précieux octets! Dans le cas où le navigateur ne prend pas en charge l'API element.animate, Angular 6.0 revient à l'utilisation des images clés CSS.

Éléments angulaires

Angular Elements est un projet qui vous permet d'encapsuler des composants Angular en tant que composants Web et de les incorporer dans une application qui n'utilise pas Angular. Au départ, ce projet n'existait que dans le «Angular Lab» (c'est-à-dire qu'il est encore expérimental). Avec la v6, il vient un peu au premier plan et est officiellement inclus dans le cadre. C'est un gros sujet qui mérite un article séparé.

ElementRef <T>

Si vous souhaitez prendre un lien d'élément dans votre modèle, vous pouvez utiliser @ViewChild ou @ViewChildren , ou même implémenter directement ElementRef . L'inconvénient dans ce cas est le suivant: dans Angular 5.0 ou inférieur, le ElementRef spécifié obtiendra le type any pour la propriété nativeElement .

Dans Angular 6.0, vous pouvez taper ElementRef plus strictement si vous le souhaitez:

 @ViewChild('loginInput') loginInput: ElementRef<HTMLInputElement>; ngAfterViewInit() { // nativeElement  `HTMLInputElement` this.loginInput.nativeElement.focus(); } 

Ce qui est reconnu comme indésirable et ce qui change fondamentalement

Parlons de ce que vous devez garder à l'esprit lorsque vous vous lancez dans la migration!

preserveWhitespaces : false par défaut

Dans la section «Problèmes pouvant survenir lors de la mise à niveau», nous notons que reserveWhitespaces est désormais false par défaut. Cette option est apparue dans Angular 4.4, et si vous vous demandez à quoi vous attendre en même temps - voici un article entier sur ce sujet. Spoiler: tout peut faire, ou il peut complètement casser vos modèles.

ngModel et formes réactives

Auparavant, il était possible de fournir le même champ de formulaire avec ngModel et formControl , mais aujourd'hui cette pratique est considérée comme indésirable et ne sera plus prise en charge dans Angular 7.0.

Il y a un peu de confusion ici, et tout le mécanisme a fonctionné, peut-être pas comme vous l' ngModel ( ngModel - c'était une directive que vous ne connaissiez pas depuis longtemps, mais l'entrée / sortie de la directive formControl , qui effectue presque la même tâche, mais pas la même).

Alors maintenant, si nous appliquons le code:

 <input [(ngModel)]="user.name" [formControl]="nameCtrl"> 

alors nous recevons un avertissement.

Vous pouvez configurer l'application pour afficher un avertissement de always ( once ), once (une fois) ou never (jamais). La valeur par défaut est always .

 imports: [ ReactiveFormsModule.withConfig({ warnOnNgModelWithFormControl: 'never' }); ] 

D'une manière ou d'une autre, en préparant la transition vers Angular 7, vous devez adapter le code pour utiliser des formulaires orientés modèle ou des formulaires réactifs.

Projet Ivy: nouveau (nouveau) moteur de rendu dans Angular

Soooo ... Il s'agit de la quatrième version angulaire majeure (2, 4, 5, 6), et le moteur de rendu est en cours de réécriture pour la troisième fois!

N'oubliez pas: Angular compile vos modèles en code TypeScript équivalent. Ensuite, ce TypeScript est compilé avec le TypeScript que vous avez écrit en JavaScript, et le résultat est à la disposition de l'utilisateur. Et nous avons déjà devant nous la 3e version de ce moteur de rendu dans Angular (la première était dans la version initiale d'Angular 2.0, et la seconde dans Angular 4.0).

Dans cette nouvelle version du moteur de rendu, l'approche de l'écriture des modèles ne change pas, cependant, elle optimise un certain nombre d'indicateurs, notamment:

  • Construire le temps
  • Taille du cadran

Tout cela est encore profondément expérimental, et le nouveau moteur de rendu Ivy est activé par une case à cocher, que vous devez mettre dans les options du compilateur (dans le fichier tsconfig.json ) si vous voulez l'essayer.

 "angularCompilerOptions": { "enableIvy": true } 

Veuillez noter que ce mécanisme n'est peut-être pas trop fiable, alors ne l'utilisez pas encore en production. Peut-être qu'il ne fonctionne toujours pas. Mais dans un avenir proche, elle sera acceptée comme option par défaut, vous devriez donc l'essayer une fois, voir si cela fonctionne dans votre application et ce que vous en retirez.

Voyons plus en détail en quoi Ivy diffère de l'ancien moteur de rendu.

Code généré par l'ancien moteur

Regardons un petit exemple: ayons un composant PonyComponent qui prend le modèle PonyModel (avec les paramètres de name et de color ) et affiche l'image du poney (selon la combinaison), ainsi que le nom du poney.

Cela ressemble à ceci:

 @Component({ selector: 'ns-pony', template: `<div> <ns-image [src]="getPonyImageUrl()"></ns-image> <div></div> </div>` }) export class PonyComponent { @Input() ponyModel: PonyModel; getPonyImageUrl() { return `images/${this.ponyModel.color}.png`; } } 

Le moteur de rendu introduit dans Angular 4 a généré une classe appelée ngfactory pour chaque modèle. La classe contenait généralement (code simplifié):

 export function View_PonyComponent_0() { return viewDef(0, [ elementDef(0, 0, null, null, 4, "div"), elementDef(1, 0, null, null, 1, "ns-image", View_ImageComponent_0), directiveDef(2, 49152, null, 0, i2.ImageComponent, { src: [0, "src"] }), elementDef(3, 0, null, null, 1, "div"), elementDef(4, null, ["", ""]) ], function (check, view) { var component = view.component; var currVal_0 = component.getPonyImageUrl(); check(view, 2, 0, currVal_0); }, function (check, view) { var component = view.component; var currVal_1 = component.ponyModel.name; check(view, 4, 0, currVal_1); }); } 

Il est difficile à lire, mais les principales parties de ce code sont décrites comme suit:

  • La structure du DOM créé, qui contient les définitions des éléments ( figure , img , figcaption ), leurs attributs et les définitions des nœuds de texte. Chaque élément de la structure DOM dans le tableau de définitions de vues est représenté par son propre index.
  • Changer les fonctions de détection; le code qu'ils contiennent vérifie si les expressions utilisées dans le modèle donnent les mêmes valeurs qu'auparavant. Ici, le résultat de la méthode getPonyImageUrl est getPonyImageUrl et, s'il change, la valeur d'entrée du composant image est mise à jour. La même chose s'applique au surnom de poney: s'il change, le nœud de texte qui contient ce surnom sera mis à jour.

Code généré par Ivy

Si nous travaillons avec Angular 6 et que le drapeau enableIvy défini sur true , une ngfactory distincte ngfactory sera pas générée dans le même exemple; Les informations seront directement intégrées dans le champ statique du composant lui-même (code simplifié):

 export class PonyComponent { static ngComponentDef = defineComponent({ type: PonyComponent, selector: [['ns-pony']], factory: () => new PonyComponent(), template: (renderFlag, component) { if (renderFlag & RenderFlags.Create) { elementStart(0, 'figure'); elementStart(1, 'ns-image'); elementEnd(); elementStart(2, 'div'); text(3); elementEnd(); elementEnd(); } if (renderFlag & RenderFlags.Update) { property(1, 'src', component.getPonyImageUrl()); text(3, interpolate('', component.ponyModel.name, '')); } }, inputs: { ponyModel: 'ponyModel' }, directives: () => [ImageComponent]; }); // ...   } 

Maintenant, tout est contenu dans ce champ statique. L'attribut template contient l'équivalent de la ngfactory familière, avec une structure légèrement différente. La fonction de template , comme précédemment, sera lancée à chaque changement, mais elle a maintenant deux modes:

  • Mode de création: le composant est en cours de création, il contient les nœuds DOM statiques à créer
  • Le reste de la fonction est exécuté à chaque modification (si nécessaire, met à jour la source d'image et le nœud de texte).

Qu'est-ce que cela change?

Désormais, tous les décorateurs sont intégrés directement dans leurs classes (les mêmes pour @Injectable , @Pipe , @Directive ), et pour les générer, il vous suffit de connaître le décorateur actuel. Ce phénomène est appelé par l'équipe Angular le «principe de localité»: pour recompiler un composant, il n'est pas nécessaire de ré-analyser l'application.

Le code généré est légèrement réduit, mais plus important encore, il est possible d'éliminer un certain nombre de dépendances, accélérant ainsi la recompilation si l'une des parties de l'application change. De plus, avec les collecteurs modernes, par exemple Webpack, tout se révèle beaucoup plus joli: le code non fonctionnel est coupé en toute sécurité, les parties du framework que vous n'utilisez pas. Par exemple, si vous n'avez pas de canaux dans l'application, le cadre nécessaire à leur interprétation n'est même pas inclus dans l'ensemble final.

Nous sommes habitués à rendre le code angulaire lourd. Parfois, ce n'est pas effrayant, mais Hello World pesant 37 ko après minification et compression est trop. Quand Ivy est responsable de la génération du code, le code non fonctionnel est coupé beaucoup plus efficacement. Maintenant, Hello World après la minification est compressé à 7,3 ko, et après compression - seulement à 2,7 ko, et c'est une grande différence. Application TodoMVC après compression - seulement 12,2 ko. Ce sont des données de l'équipe Angular, et d'autres n'ont pas pu fonctionner avec nous, car pour qu'Ivy fonctionne comme décrit ici, vous devez toujours le corriger manuellement.

Pour plus de détails, consultez cette présentation avec ng-conf.

Compatibilité avec les bibliothèques existantes

Vous pourriez être intéressé par: qu'adviendra-t-il des bibliothèques qui sont déjà publiées dans l'ancien format si Ivy est utilisé dans votre projet? Ne vous inquiétez pas: le moteur fera une version compatible Ivy des dépendances de votre projet, même si elles sont compilées sans Ivy. Je n'exposerai pas l'intérieur pour le moment, mais tous les détails doivent être transparents.

De nouvelles fonctionnalités

Examinons les nouvelles opportunités que nous aurons en travaillant avec ce moteur d'affichage.

Propriétés privées dans les modèles

Un nouveau moteur ajoute une nouvelle fonctionnalité ou un changement potentiel.
Cette situation est directement liée au fait que la fonction modèle est intégrée dans le champ statique du composant: nous pouvons maintenant utiliser les propriétés privées de nos composants dans les modèles. Cela était auparavant impossible, à cause de quoi nous avons été obligés de garder publics tous les champs et méthodes du composant utilisé dans le modèle, et ils sont tombés dans une classe séparée ( ngfactory ). Lors de l'accès à une propriété privée à partir d'une autre classe, la compilation TypeScript échouait. Maintenant, c'est du passé: la fonction de modèle étant dans un champ statique, elle a accès aux propriétés privées du composant.

J'ai vu un commentaire des membres de l'équipe Angular sur le fait qu'il n'est pas recommandé d'utiliser des propriétés privées dans les modèles, bien que cela soit maintenant possible - car cela peut être à nouveau interdit à l'avenir ... par conséquent, il est probablement plus sage de continuer à utiliser uniquement des champs publics dans les modèles! Dans tous les cas, l'écriture de tests unitaires est maintenant plus facile, car le test peut vérifier l'état d'un composant sans même générer et vérifier le DOM pour cela.

i18n à l'exécution

Attention: le nouveau moteur de rendu nous ouvre enfin une opportunité tant attendue et donne «i18n à l'exécution». Au moment d'écrire ces lignes, elle n'était pas encore tout à fait prête, mais nous avons vu plusieurs commits à la fois, et c'est bon signe!
Ce qui est cool, c'est que vous n'avez pas à changer à peu près votre application si vous travaillez déjà avec i18n. Mais maintenant, vous n'avez pas besoin de reconstruire l'application pour chaque paramètre régional que vous prévoyez de prendre en charge - téléchargez simplement JSON avec des traductions pour chaque paramètre régional, et Angular se chargera du reste!

Bibliothèques AoT

Actuellement, une bibliothèque publiée dans NPM doit publier le fichier metadata.json et ne peut pas publier le code AoT de ses composants. C'est triste, car les coûts associés à un tel assemblage sont répercutés sur notre application. Avec Ivy, aucun fichier de métadonnées n'est nécessaire et les auteurs de bibliothèque pourront désormais publier leur code AoT directement dans NPM!

Pistes de pile améliorées

Maintenant, le code généré devrait donner des traces de pile améliorées, si vous rencontrez un problème avec vos modèles - entraînez une erreur nette indiquant la ligne du modèle dans laquelle il s'est produit. Vous pouvez même définir des points d'arrêt dans les modèles et suivre ce qui se passe réellement dans Angular.

NgModule disparaître?

C'est encore une perspective lointaine, mais peut-être qu'à l'avenir il sera possible de se passer de NgModules. Les premiers signes de tels changements sont des fournisseurs pouvant être secoués dans les arbres, et il est logique de supposer qu'Ivy possède tous les blocs de base nécessaires pour ceux qui sont prêts à abandonner progressivement les NgModules (ou, du moins, à les rendre moins réactifs). Certes, tout cela est encore à l'avenir, nous serons patients.

Il n'y aura pas beaucoup de nouvelles fonctionnalités dans cette version, mais Ivy est certainement intéressant pour l'avenir. Expérimentez avec - je me demande comment vous l'aimerez!

Source: https://habr.com/ru/post/fr419389/


All Articles