Motor de renderizado angular 6 e Ivy

imagen Buenas tardes, colegas. Estamos considerando actualizar el libro de Jacob Fine y Anton Moiseev " Angular y TypeScript. Creación de sitios web para profesionales ". Este otoño sale una nueva edición e incluye material en Angular 5 y 6.

Al principio pensamos publicar material sobre el motor Ivy, que probablemente sea la innovación más interesante en Angular 6, pero luego nos detuvimos en una publicación más general de Cedric Exbright (el original fue lanzado en mayo).

En Angular 6, hubo muchas innovaciones serias, además, la más importante de ellas no puede nombrar características: este es Ivy, un nuevo motor de renderizado. Dado que el motor todavía es experimental, hablaremos sobre él al final de este artículo y comenzaremos con otras características nuevas y cambios revolucionarios.

Proveedores sacudibles de árboles

Ahora hay una nueva forma recomendada de registrar al proveedor directamente en el decorador @Injectable() , utilizando el nuevo atributo providedIn . Toma 'root' como el valor de cualquier módulo en su aplicación. Al usar 'root' objeto implementado se registrará en la aplicación como un solitario, y no necesitará agregarlo a los proveedores en el módulo raíz. De manera similar, cuando se usa providedIn: UsersModule objeto implementado se registra como el proveedor UsersModule y no se agrega a los proveedores del módulo.

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

Este nuevo método se introdujo para eliminar mejor el código no funcional en la aplicación (sacudida de árbol). En la actualidad, la situación es tal que el servicio que se agrega a los proveedores del módulo terminará en el conjunto final, incluso si no se usa en la aplicación, y permitir esto es un poco triste. Si utiliza la carga diferida, puede caer en varias trampas a la vez o encontrarse en una situación en la que el servicio se ingresará en el conjunto incorrecto.

Es poco probable que ocurra tal situación en las aplicaciones (si escribe un servicio y luego lo usa), pero los módulos de terceros a veces ofrecen servicios que no necesitamos; como resultado, tenemos un montón de JavaScript inútil.

Por lo tanto, esta característica será especialmente útil para los desarrolladores de bibliotecas, pero ahora se recomienda registrar los objetos implementados de esta manera; esto también se aplica a los desarrolladores de aplicaciones. La nueva CLI ahora incluso utiliza el andamiaje providedIn: 'root' por defecto" cuando trabaja con servicios.

En la misma línea, ahora puede declarar un InjectionToken , registrarlo directamente con providedIn y agregar una factory aquí:

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

Tenga en cuenta: esto también simplifica las pruebas unitarias. A los fines de tales pruebas, se utilizan para registrar el servicio con los proveedores del módulo de prueba. Esto es lo que hicimos antes:

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

Ahora, si el UserService usa provideIn providedIn: 'root' :

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

Simplemente no se preocupe: todos los servicios registrados con providedIn no se cargan en la prueba, sino que se crean instancias perezosamente, solo en los casos en que realmente se necesitan.

Rxjs 6

Angular 6 ahora usa RxJS 6 internamente, por lo que debe actualizar la aplicación teniendo esto en cuenta.

Y ... ¡RxJS 6 está cambiando el enfoque para importar!

En RxJS 5, podrías escribir:

 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); 

En RxJS 5.5, aparecieron declaraciones canalizables:

 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) ); 

Y en RxJS 6.0, las importaciones han cambiado:

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

Entonces, un día tendrá que cambiar las importaciones dentro de toda la aplicación. Escribo "una vez", y no "en este momento", porque la biblioteca rxJs-compat se lanzó en RxJS, que le permite descargar RxJS a la versión 6.0, incluso si las versiones anteriores todavía se utilizan en toda su aplicación o en una de las bibliotecas utilizadas sintaxis

El equipo de Angular ha escrito un documento completo sobre este tema, y ​​es absolutamente necesario leerlo antes de migrar a Angular 6.0.

Tenga en cuenta: aquí hay un conjunto de reglas tslint muy bueno llamado rxjs-tslint . Solo hay 4 reglas, y si las agrega al proyecto, el sistema migrará automáticamente todas sus importaciones y código RxJS, y esto se hace con la tslint --fix más tslint --fix ¡ tslint --fix ! Después de todo, si aún no lo sabe, ¡en tslint hay una opción de fix que corrige automáticamente todos los errores que encuentra! Se puede usar aún más simple: instale globalmente rxjs-tslint y ejecute rxjs-5-to-6-migrate -p src/tsconfig.app.json . rxjs-tslint en uno de nuestros proyectos y funcionó bastante bien (ejecútelo al menos dos veces para colapsar también todas las importaciones). Consulte el archivo README de este proyecto para obtener más detalles: github.com/ReactiveX/rxjs-tslint .

Si está interesado en aprender más sobre RxJS 6.0, le recomiendo el próximo informe de Ben Lesch sobre ng-conf.

i18n

La perspectiva más importante asociada con i18n es la capacidad de hacer "i18n en tiempo de ejecución", sin tener que construir la aplicación por separado para cada punto local. Esta característica aún no está disponible (solo hay prototipos), y se necesitará el motor Ivy para su funcionamiento (más información a continuación).

Otro cambio relacionado con i18n ya ha tenido lugar y está disponible. El canal de divisas está optimizado de la manera más eficiente: ahora redondea todas las monedas no a 2 dígitos, como antes, sino a la cantidad deseada de dígitos (por ejemplo, a 3 en el caso del dinar bahreiní o a 0 para el peso chileno).

Si es necesario, este valor se puede recuperar mediante programación utilizando la nueva función i18n getNumberOfCurrencyDigits .

Otras funciones de formateo convenientes, como formatDate , formatCurrency , formatPercent y formatNumber también aparecieron en el formatNumber formatPercent .

Convenientemente, si desea aplicar las mismas transformaciones que se realizan en los canales, pero hacerlo desde el código TypeScript.

Animaciones

En Angular 6.0, las animaciones ya son posibles sin polyfill web-animations-js , a menos que use AnimationBuilder . ¡Su aplicación puede ganar algunos bytes preciosos! En caso de que el navegador no sea compatible con la API element.animate , Angular 6.0 retrocede al uso de fotogramas clave CSS.

Elementos angulares

Angular Elements es un proyecto que le permite envolver componentes angulares como componentes web e incrustarlos en una aplicación que no utiliza Angular. Al principio, este proyecto existía solo en el "Laboratorio Angular" (es decir, todavía es experimental). Con v6, se destaca un poco y se incluye oficialmente en el marco. Este es un gran tema que merece un artículo separado.

ElementRef <T>

Si desea tomar un enlace de elemento en su plantilla, puede usar @ViewChild o @ViewChildren , o incluso implementar directamente ElementRef . El inconveniente en este caso es este: en Angular 5.0 o inferior, ElementRef especificado obtendrá el tipo any para la propiedad nativeElement .

En Angular 6.0 puede escribir ElementRef más estricto si lo desea:

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

Lo que se reconoce como indeseable y lo que está cambiando fundamentalmente

¡Hablemos de lo que debe tener en cuenta al embarcarse en la migración!

preserveWhitespaces : valor predeterminado false

En la sección "Problemas que pueden ocurrir durante la actualización", observamos que preserveWhitespaces ahora es false de forma predeterminada. Esta opción apareció en Angular 4.4, y si se pregunta qué esperar al mismo tiempo, aquí hay una publicación completa sobre este tema. Spoiler: todo puede hacer, o puede romper completamente tus plantillas.

ngModel y formas reactivas

Anteriormente, era posible proporcionar el mismo campo de formulario con ngModel y formControl , pero hoy esta práctica se considera indeseable y ya no será compatible con Angular 7.0.

Aquí hay un poco de confusión, y todo el mecanismo funcionó, tal vez no como esperaba ( ngModel : esta era una directiva que no le resultaba familiar hace mucho tiempo, pero la entrada / salida de la directiva formControl , que realiza casi la misma tarea, pero no la misma).

Entonces, si aplicamos el código:

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

entonces recibimos una advertencia.

Puede configurar la aplicación para mostrar una advertencia de always ( once ), once (una vez) o never (nunca). El valor predeterminado es always .

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

De una forma u otra, al prepararse para la transición a Angular 7, debe adaptar el código para usar formas orientadas a plantillas o formas reactivas.

Proyecto Ivy: nuevo (nuevo) motor de renderizado en Angular

Soooo ... Esta es la cuarta versión principal de Angular (2, 4, 5, 6), ¡y el motor de renderizado se está reescribiendo por tercera vez!

Recuerde: Angular compila sus plantillas en código TypeScript equivalente. Luego, este TypeScript se compila con el TypeScript que escribió en JavaScript, y el resultado está a disposición del usuario. Y ante nosotros ya está la tercera versión de este motor de renderizado en Angular (la primera fue en la versión inicial de Angular 2.0 y la segunda en Angular 4.0).

En esta nueva versión del motor de renderizado, el enfoque para escribir plantillas no cambia, sin embargo, optimiza una serie de indicadores, en particular:

  • Tiempo de construcción
  • Tamaño del dial

Todo esto sigue siendo profundamente experimental, y el nuevo motor de renderizado Ivy se activa mediante una casilla de verificación, que debe poner en las opciones del compilador (en el archivo tsconfig.json ) si desea probarlo.

 "angularCompilerOptions": { "enableIvy": true } 

Tenga en cuenta que este mecanismo quizás no sea demasiado confiable, por lo tanto, no lo use en producción todavía. Quizás él todavía no trabaja. Pero en el futuro cercano se aceptará como la opción predeterminada, por lo que debe probarlo una vez, ver si funciona en su aplicación y qué beneficios le ofrece.

Analicemos con más detalle cómo Ivy difiere del motor de renderizado anterior.

Código generado por el viejo motor

Veamos un pequeño ejemplo: tengamos un componente PonyComponent que tome el modelo PonyModel (con los parámetros de name y color ) y muestre la imagen del pony (dependiendo del traje), así como el nombre del pony.

Se ve así:

 @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`; } } 

El motor de renderizado introducido en Angular 4 generó una clase llamada ngfactory para cada plantilla. La clase generalmente contenía (código simplificado):

 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); }); } 

Es difícil de leer, pero las partes principales de este código se describen a continuación:

  • La estructura del DOM creado, que contiene las definiciones de elementos ( figure , img , figcaption ), sus atributos y definiciones de nodos de texto. Cada elemento de la estructura DOM en el conjunto de definiciones de vista está representado por su propio índice.
  • Cambiar las funciones de detección; el código contenido en ellos verifica si las expresiones utilizadas en la plantilla producen los mismos valores que antes. Aquí, se getPonyImageUrl el resultado del método getPonyImageUrl y, si cambia, se actualiza el valor de entrada para el componente de imagen. Lo mismo se aplica al apodo de pony: si cambia, el nodo de texto que contiene este apodo se actualizará.

Ivy generó código

Si trabajamos con Angular 6, y el indicador enableIvy establece en true , no se generará una ngfactory separada en el mismo ejemplo; La información se incrustará directamente en el campo estático del componente en sí (código simplificado):

 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]; }); // ...   } 

Ahora todo está contenido en este campo estático. El atributo de template contiene el equivalente de la ngfactory familiar, con una estructura ligeramente diferente. La función de template , como antes, se iniciará ante cualquier cambio, pero ahora tiene dos modos:

  • Modo de creación: el componente recién se está creando, contiene los nodos DOM estáticos que deben crearse
  • El resto de la función se ejecuta con cada cambio (si es necesario, actualiza la fuente de la imagen y el nodo de texto).

¿Qué cambia eso?

Ahora todos los decoradores están integrados directamente en sus clases (lo mismo para @Injectable , @Pipe , @Directive ), y para generarlos solo necesita saber sobre el decorador actual. El equipo de Angular llama a este fenómeno el "principio de localidad": para volver a compilar un componente, no es necesario volver a analizar la aplicación.

El código generado se reduce ligeramente, pero lo que es más importante, es posible eliminar una serie de dependencias, lo que acelera la recompilación si una de las partes de la aplicación cambia. Además, con los coleccionistas modernos, por ejemplo, Webpack, todo resulta mucho más bonito: el código no funcional se corta de forma segura, esas partes del marco que no se utilizan. Por ejemplo, si no tiene canales en la aplicación, el marco necesario para su interpretación ni siquiera se incluye en el conjunto final.

Estamos acostumbrados a hacer pesado el código angular. A veces no da miedo, pero Hello World pesa 37 kb después de la minificación y la compresión es demasiado. Cuando Ivy es responsable de generar el código, el código no funcional se corta de manera mucho más eficiente. Ahora Hello World después de la minificación se comprime a 7.3 kb, y después de la compresión, solo a 2.7 kb, y esta es una gran diferencia. Aplicación TodoMVC después de la compresión: solo 12.2 kb. Estos son datos del equipo de Angular, y otros no pudieron trabajar con nosotros, ya que para que Ivy funcione como se describe aquí, aún necesita parchearlo manualmente.

Para más detalles, mira esta charla con ng-conf.

Compatibilidad con bibliotecas existentes.

Quizás le interese: ¿qué pasará con las bibliotecas que ya están publicadas en el formato anterior si Ivy se usa en su proyecto? No se preocupe: el motor creará una versión compatible con Ivy de las dependencias de su proyecto, incluso si se compilan sin Ivy. No expondré el interior en este momento, pero todos los detalles deben ser transparentes.

Nuevas características

Consideremos qué nuevas oportunidades tendremos cuando trabajemos con este motor de visualización.

Propiedades privadas en plantillas

Un nuevo motor agrega una nueva característica o un posible cambio.
Esta situación está directamente relacionada con el hecho de que la función de plantilla está incrustada en el campo estático del componente: ahora podemos usar las propiedades privadas de nuestros componentes en las plantillas. Esto era anteriormente imposible, por lo que nos vimos obligados a mantener públicos todos los campos y métodos del componente utilizado en la plantilla, y cayeron en una clase separada ( ngfactory ). Al acceder a una propiedad privada desde otra clase, la compilación de TypeScript fallará. Ahora está en el pasado: dado que la función de plantilla está en un campo estático, tiene acceso a las propiedades privadas del componente.

Vi un comentario de los miembros del equipo de Angular sobre el hecho de que no se recomienda usar propiedades privadas en las plantillas, aunque esto ahora es posible, ya que puede estar prohibido nuevamente en el futuro ... por lo tanto, ¡probablemente sea más prudente continuar usando solo campos públicos en las plantillas! En cualquier caso, escribir pruebas unitarias ahora es más fácil, porque la prueba puede verificar el estado de un componente sin siquiera generar y verificar el DOM para esto.

i18n en tiempo de ejecución

Tenga en cuenta: el nuevo motor de renderizado finalmente abre una oportunidad muy esperada para nosotros y ofrece "i18n en tiempo de ejecución". Al momento de escribir, todavía no estaba lista, pero vimos varias confirmaciones a la vez, ¡y esta es una buena señal!
Lo bueno es que no tiene que cambiar su aplicación si ya está trabajando con i18n. Pero ahora no necesita reconstruir la aplicación para cada configuración regional que planea admitir: simplemente cargue JSON con traducciones para cada configuración regional, y Angular se encargará del resto.

Bibliotecas AoT

Actualmente, una biblioteca lanzada en NPM debe publicar el archivo metadata.json y no puede publicar el código AoT de sus componentes. Esto es triste, ya que los costos asociados con dicho ensamblado se transfieren a nuestra aplicación. Con Ivy, no hay necesidad de un archivo de metadatos, ¡y los autores de la biblioteca ahora podrán publicar su código AoT directamente en NPM!

Pistas de pila mejoradas

Ahora, el código generado debería proporcionar trazas de pila mejoradas, si tiene un problema con sus plantillas, se genera un error claro que indica la línea de la plantilla en la que se produjo. Incluso puede establecer puntos de interrupción en las plantillas y realizar un seguimiento de lo que realmente sucede en Angular.

NgModule desaparecerá?

Esto todavía es una perspectiva lejana, pero quizás en el futuro será posible prescindir de NgModules. Los primeros signos de tales cambios son los proveedores que se pueden sacudir en árbol, y es lógico suponer que Ivy tiene todos los bloques básicos necesarios para aquellos que están listos para abandonar gradualmente NgModules (o, al menos, hacerlos menos receptivos). Es cierto, todo esto todavía está en el futuro, seremos pacientes.

No habrá muchas características nuevas en esta versión, pero Ivy es definitivamente interesante para el futuro. Experimente con él. Me pregunto cómo le gustará.

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


All Articles