Todas las ventajas, sin complicaciones: un tutorial de Angular 9

Publicado: 2022-03-11

“Cada año se rompe Internet”, dice el dicho, y los desarrolladores generalmente tienen que ir y arreglarlo. Con la tan esperada versión 9 de Angular, uno podría pensar que esto se aplicaría, y las aplicaciones desarrolladas en versiones anteriores tendrían que pasar por un importante proceso de migración.

¡Pero ese no es el caso! El equipo de Angular rediseñó por completo su compilador, lo que resultó en compilaciones más rápidas, ejecuciones de prueba más rápidas, tamaños de paquete más pequeños y, lo que es más importante, compatibilidad con versiones anteriores. Con Angular 9, los desarrolladores básicamente obtienen todas las ventajas sin ninguna molestia.

En este tutorial de Angular 9, crearemos una aplicación Angular desde cero. Usaremos algunas de las funciones más recientes de Angular 9 y repasaremos otras mejoras en el camino.

Tutorial de Angular 9: Comenzando con una nueva aplicación de Angular

Comencemos con nuestro ejemplo de proyecto Angular. Primero, instalemos la última versión de la CLI de Angular:

 npm install -g @angular/cli

Podemos verificar la versión de Angular CLI ejecutando ng version .

A continuación, creemos una aplicación Angular:

 ng new ng9-app --create-application=false --strict

Estamos usando dos argumentos en nuestro ng new :

  • --create-application=false le indicará a la CLI que solo genere archivos de espacio de trabajo. Esto nos ayudará a organizar mejor nuestro código cuando necesitemos tener más de una aplicación y varias bibliotecas.
  • --strict agregará reglas más estrictas para aplicar más tipeo de TypeScript y limpieza del código.

Como resultado de esto, tenemos una carpeta y archivos de espacio de trabajo básicos.

Captura de pantalla de un IDE que muestra la carpeta ng9-app, que contiene node_modules, .editorconfig, .gitignore, angular.json, package-lock.json, package.json, README.md, tsconfig.json y tslint.json.

Ahora, agreguemos una nueva aplicación. Para ello ejecutaremos:

 ng generate application tv-show-rating

Se nos pedirá:

 ? Would you like to share anonymous usage data about this project with the Angular Team at Google under Google's Privacy Policy at https://policies.google.com/privacy? For more details and how to change this setting, see http://angular.io/analytics. No ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? SCSS

Ahora, si ejecutamos ng serve , veremos que la aplicación se ejecuta con su andamiaje inicial.

Una captura de pantalla del andamiaje de Angular 9, con un aviso de que "¡la aplicación de calificación de programas de televisión se está ejecutando!" También hay enlaces a recursos y próximos pasos.

Si ejecutamos ng build --prod , podemos ver la lista de archivos generados.

Una captura de pantalla de la salida "ng build --prod" de Angular 9. Comienza con "Generación de paquetes de ES5 para carga diferencial...". Una vez hecho esto, enumera varios fragmentos de archivos de JavaScript (tiempo de ejecución, polyfills y main, cada uno con una versión -es2015 y -es5) y un archivo CSS. La línea final proporciona una marca de tiempo, hash y un tiempo de ejecución de 23 881 milisegundos.

Tenemos dos versiones de cada archivo. Uno es compatible con los navegadores heredados y el otro está compilado para ES2015, que usa API más nuevas y requiere menos rellenos poligráficos para ejecutarse en los navegadores.

Una gran mejora de Angular 9 es el tamaño del paquete. Según el equipo de Angular, puedes ver una disminución de hasta un 40 % para las aplicaciones grandes.

Para una aplicación recién creada, el tamaño del paquete es bastante similar al de Angular 8, pero a medida que su aplicación crece, verá que el tamaño del paquete se vuelve más pequeño en comparación con las versiones anteriores.

Otra característica introducida en Angular 9 es la capacidad de advertirnos si alguno de los archivos CSS de estilo de componente es mayor que un umbral definido.

Una captura de pantalla de la sección "presupuestos" de un archivo de configuración JSON de Angular 9, con dos objetos en una matriz. El primer objeto tiene "tipo" establecido en "inicial", "maximumWarning" establecido en "2mb" y "maximumError" establecido en "5mb". El segundo objeto tiene "type" establecido en "anyComponentStyle", "maximumWarning" establecido en "6kb" y "maximumError" establecido en "10kb".

Esto nos ayudará a detectar importaciones de estilos incorrectos o archivos de estilos de componentes enormes.

Agregar un formulario para calificar programas de televisión

A continuación, agregaremos un formulario para calificar los programas de TV. Para eso, primero, instalaremos bootstrap y ng-bootstrap :

 npm install bootstrap @ng-bootstrap/ng-bootstrap

Otra mejora en Angular 9 es i18n (internacionalización). Anteriormente, los desarrolladores debían ejecutar una compilación completa para cada configuración regional en una aplicación. En cambio, Angular 9 nos permite compilar una aplicación una vez y generar todos los archivos i18n en un proceso posterior a la compilación, lo que reduce significativamente el tiempo de compilación. Dado que ng-bootstrap depende de i18n, agregaremos el nuevo paquete a nuestro proyecto:

 ng add @angular/localize

A continuación, agregaremos el tema Bootstrap al styles.scss de nuestra aplicación:

 @import "~bootstrap/scss/bootstrap";

E incluiremos NgbModule y ReactiveFormsModule en nuestro AppModule en app.module.ts :

 // ... import { ReactiveFormsModule } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ imports: [ // ... ReactiveFormsModule, NgbModule ], })

A continuación, actualizaremos app.component.html con una cuadrícula básica para nuestro formulario:

 <div class="container"> <div class="row"> <div class="col-6"> </div> </div> </div>

Y generar el componente de formulario:

 ng gc TvRatingForm

tv-rating-form.component.html y agreguemos el formulario para calificar programas de TV.

 <form [formGroup]="form" (ngSubmit)="submit()" class="mt-3"> <div class="form-group"> <label>TV SHOW</label> <select class="custom-select" formControlName="tvShow"> <option *ngFor="let tvShow of tvShows" [value]="tvShow.name">{{tvShow.name}}</option> </select> </div> <div class="form-group"> <ngb-rating [max]="5" formControlName="rating"></ngb-rating> </div> <button [disabled]="form.invalid || form.disabled" class="btn btn-primary">OK</button> </form>

Y tv-rating-form.component.ts se verá así:

 // ... export class TvRatingFormComponent implements OnInit { tvShows = [ { name: 'Better call Saul!' }, { name: 'Breaking Bad' }, { name: 'Lost' }, { name: 'Mad men' } ]; form = new FormGroup({ tvShow: new FormControl('', Validators.required), rating: new FormControl('', Validators.required), }); submit() { alert(JSON.stringify(this.form.value)); this.form.reset(); } }

Finalmente, agreguemos el formulario a app.component.html :

 <!-- ... --> <div class="col-6"> <app-tv-rating-form></app-tv-rating-form> </div>

En este punto, tenemos algunas funciones básicas de interfaz de usuario. Ahora, si ejecutamos ng serve nuevamente, podemos verlo en acción.

Una captura de pantalla de una aplicación de tutorial de Angular 9 que muestra un formulario titulado "PROGRAMA DE TV", con un menú desplegable que enumera varios títulos de programas, un medidor de estrellas y un botón Aceptar. En la animación, el usuario selecciona un programa, selecciona una calificación y luego hace clic en el botón Aceptar.

Antes de continuar, echemos un vistazo rápido a algunas características nuevas e interesantes de Angular 9 que se agregaron para ayudar con la depuración. Dado que esta es una tarea muy común en nuestro trabajo diario, vale la pena saber qué ha cambiado para hacernos la vida un poco más fácil.

Depuración con Angular 9 Ivy

Otra gran mejora introducida en Angular 9 y Angular Ivy es la experiencia de depuración. El compilador ahora puede detectar más errores y arrojarlos de una manera más "legible".

Veámoslo en acción. Primero, activaremos la comprobación de plantillas en tsconfig.json :

 { // ... "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictTemplates": true } }

Ahora, si actualizamos la matriz tvShows y renombramos name a title :

 tvShows = [ { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' } ];

…obtendremos un error del compilador.

Una captura de pantalla de la salida del compilador Angular 9/Angular Ivy, con un nombre de archivo y una posición, que dice "error TS2339: la propiedad 'nombre' no existe en el tipo '{título: cadena; }'". También muestra la línea de código en cuestión y subraya la referencia, en este caso en el archivo tv-rating-form.component.html donde se menciona tvShow.name. Después de eso, la referencia a este archivo HTML se rastrea hasta el archivo TypeScript correspondiente y se resalta de manera similar.

Esta verificación de tipos nos permitirá evitar errores tipográficos y el uso incorrecto de tipos de TypeScript.

Validación de Angular Ivy para @Input()

Otra buena validación que obtenemos es con @Input() . Por ejemplo, podríamos agregar esto a tv-rating-form.component.ts :

 @Input() title: string;

… y vincularlo en app.component.html :

 <app-tv-rating-form [title]="title"></app-tv-rating-form>

… y luego cambie app.component.ts así:

 // ... export class AppComponent { title = null; }

Si hacemos estos tres cambios, obtendremos otro tipo de error del compilador.

Una captura de pantalla de la salida del compilador Angular 9/Angular Ivy, en un formato similar al anterior, resaltando app.component.html con "error TS 2322: el tipo 'null' no se puede asignar al tipo 'string'".

En caso de que queramos omitirlo, podemos usar $any() en la plantilla para convertir el valor en any y corregir el error:

 <app-tv-rating-form [title]="$any(title)"></app-tv-rating-form>

Sin embargo, la forma correcta de solucionar esto sería hacer que el title del formulario sea anulable:

 @Input() title: string | null ;

El ExpressionChangedAfterItHasBeenCheckedError en Angular 9 Ivy

Uno de los errores más temidos en el desarrollo de Angular es ExpressionChangedAfterItHasBeenCheckedError . Afortunadamente, Ivy muestra el error de una manera más clara, lo que facilita encontrar de dónde proviene el problema.

Entonces, introduzcamos un error ExpressionChangedAfterItHasBeenCheckedError . Para hacer eso, primero, generaremos un servicio:

 ng gs Title

A continuación, agregaremos un BehaviorSubject y métodos para acceder al Observable y emitir un nuevo valor.

 export class TitleService { private bs = new BehaviorSubject < string > (''); constructor() {} get title$() { return this.bs.asObservable(); } update(title: string) { this.bs.next(title); } }

Después de eso, agregaremos esto a app.component.html :

 <!-- ... --> <div class="col-6"> <h2> {{title$ | async}} </h2> <app-tv-rating-form [title]="title"></app-tv-rating-form> </div>

Y en app.component.ts , inyectaremos TitleService :

 export class AppComponent implements OnInit { // ... title$: Observable < string > ; constructor( private titleSvc: TitleService ) {} ngOnInit() { this.title$ = this.titleSvc.title$; } // ... }

Finalmente, en tv-rating-form.component.ts , inyectaremos TitleService y actualizaremos el título de AppComponent , lo que arrojará un error ExpressionChangedAfterItHasBeenCheckedError .

 // ... constructor( private titleSvc: TitleService ) { } ngOnInit() { this.titleSvc.update('new title!'); }

Ahora podemos ver el error detallado en la consola de desarrollo del navegador y hacer clic en app.component.html nos indicará dónde está el error.

Una captura de pantalla de la consola de desarrollo del navegador, que muestra el informe de Angular Ivy del error ExpressionChangedAfterItHasBeenCheckedError. Un seguimiento de la pila en texto rojo indica el error, junto con los valores anteriores y actuales, y una pista. En el medio del seguimiento de la pila está la única línea que no hace referencia a core.js. El usuario hace clic en él y se lo lleva a la línea de app.component.html que está causando el error.

Podemos corregir este error envolviendo la llamada de servicio con setTimeout :

 setTimeout(() => { this.titleSvc.update('new title!'); });

Para comprender por qué ocurre el error ExpressionChangedAfterItHasBeenCheckedError y explorar otras posibilidades, vale la pena leer la publicación de Maxim Koretskyi sobre el tema.

Angular Ivy nos permite presentar los errores de una manera más clara y ayuda a hacer cumplir la escritura de TypeScript en nuestro código. En la siguiente sección, cubriremos algunos escenarios comunes en los que aprovecharemos Ivy y la depuración.

Escribir una prueba para nuestra aplicación Angular 9 con arneses de componentes

En Angular 9, se introdujo una nueva API de prueba llamada arneses de componentes . La idea detrás de esto es eliminar todas las tareas necesarias para interactuar con el DOM, lo que hace que sea mucho más fácil trabajar con él y más estable de ejecutar.

La API del arnés del componente está incluida en la biblioteca @angular/cdk , así que primero instalemos eso en nuestro proyecto:

 npm install @angular/cdk

Ahora podemos escribir una prueba y aprovechar los arneses de los componentes. En tv-rating-form.component.spec.ts , configuremos la prueba:

 import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { ReactiveFormsModule } from '@angular/forms'; describe('TvRatingFormComponent', () => { let component: TvRatingFormComponent; let fixture: ComponentFixture < TvRatingFormComponent > ; beforeEach(async (() => { TestBed.configureTestingModule({ imports: [ NgbModule, ReactiveFormsModule ], declarations: [TvRatingFormComponent] }).compileComponents(); })); // ... });

A continuación, implementemos un ComponentHarness para nuestro componente. Vamos a crear dos arneses: uno para TvRatingForm y otro para NgbRating . ComponentHarness requiere un campo static , hostSelector , que debe tomar el valor del selector del componente.

 // ... import { ComponentHarness, HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; class TvRatingFormHarness extends ComponentHarness { static hostSelector = 'app-tv-rating-form'; } class NgbRatingHarness extends ComponentHarness { static hostSelector = 'ngb-rating'; } // ...

Para nuestro TvRatingFormHarness , crearemos un selector para el botón de envío y una función para activar un click . Puede ver cuánto más fácil se vuelve implementar esto.

 class TvRatingFormHarness extends ComponentHarness { // ... protected getButton = this.locatorFor('button'); async submit() { const button = await this.getButton(); await button.click(); } }

A continuación, agregaremos métodos para establecer una calificación. Aquí usamos locatorForAll para buscar todos los elementos <span> que representan las estrellas en las que el usuario puede hacer clic. La función rate simplemente obtiene todas las estrellas de calificación posibles y hace clic en la correspondiente al valor enviado.

 class NgbRatingHarness extends ComponentHarness { // ... protected getRatings = this.locatorForAll('span:not(.sr-only)'); async rate(value: number) { const ratings = await this.getRatings(); return ratings[value - 1].click(); } }

La última pieza que falta es conectar TvRatingFormHarness a NgbRatingHarness . Para hacer eso, solo agregamos el localizador en la clase TvRatingFormHarness .

 class TvRatingFormHarness extends ComponentHarness { // ... getRating = this.locatorFor(NgbRatingHarness); // ... }

Ahora, escribamos nuestra prueba:

 describe('TvRatingFormComponent', () => { // ... it('should pop an alert on submit', async () => { spyOn(window, 'alert'); const select = fixture.debugElement.query(By.css('select')).nativeElement; select.value = 'Lost'; select.dispatchEvent(new Event('change')); fixture.detectChanges(); const harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, TvRatingFormHarness); const rating = await harness.getRating(); await rating.rate(1); await harness.submit(); expect(window.alert).toHaveBeenCalledWith('{"tvShow":"Lost","rating":1}'); }); });

Tenga en cuenta que para nuestra select dentro del formulario, no implementamos la configuración de su valor a través de un arnés. Eso es porque la API aún no admite la selección de una opción. Pero esto nos da la oportunidad de comparar aquí cómo se veía la interacción con los elementos antes de los arneses de componentes.

Una última cosa antes de hacer las pruebas. Necesitamos arreglar app.component.spec.ts ya que actualizamos el title para que sea null .

 describe('AppComponent', () => { // ... it(`should have as title 'tv-show-rating'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app.title).toEqual(null); }); });

Ahora, cuando ejecutamos ng test , nuestra prueba pasa.

Una captura de pantalla de Karma ejecutando pruebas en nuestra aplicación Angular 9. Muestra "Ejecutó 2 de 6 especificaciones" con el mensaje "Incompleto: se encontró fit() o fdescribe(), 2 especificaciones, 0 fallas, aleatorizado con semilla 69573". Las dos pruebas de TvRatingFormComponent están resaltadas. Las tres pruebas de AppComponent y la prueba de TitleService son todas grises.

Volver a nuestra aplicación de ejemplo de Angular 9: guardar datos en una base de datos

Terminemos nuestro tutorial de Angular 9 agregando una conexión a Firestore y guardando las calificaciones en la base de datos.

Para hacer eso, necesitamos crear un Proyecto Firebase. Luego, instalaremos las dependencias requeridas.

 npm install @angular/fire firebase

En la configuración del proyecto de Firebase Console, obtendremos su configuración y la agregaremos a environment.ts y environment.prod.ts :

 export const environment = { // ... firebase: { apiKey: '{your-api-key}', authDomain: '{your-project-id}.firebaseapp.com', databaseURL: 'https://{your-project-id}.firebaseio.com', projectId: '{your-project-id}', storageBucket: '{your-project-id}.appspot.com', messagingSenderId: '{your-messaging-id}', appId: '{your-app-id}' } };

Después de eso, importaremos los módulos necesarios en app.module.ts :

 import { AngularFireModule } from '@angular/fire'; import { AngularFirestoreModule } from '@angular/fire/firestore'; import { environment } from '../environments/environment'; @NgModule({ // ... imports: [ // ... AngularFireModule.initializeApp(environment.firebase), AngularFirestoreModule, ], // ... })

A continuación, en tv-rating-form.component.ts , inyectaremos el servicio AngularFirestore y guardaremos una nueva calificación en el envío del formulario:

 import { AngularFirestore } from '@angular/fire/firestore'; export class TvRatingFormComponent implements OnInit { constructor( // ... private af: AngularFirestore, ) { } async submit(event: any) { this.form.disable(); await this.af.collection('ratings').add(this.form.value); this.form.enable(); this.form.reset(); } } 

Una captura de pantalla de una aplicación de tutorial de Angular 9 que muestra un formulario titulado "PROGRAMA DE TV" debajo de un título de página más grande "¡título nuevo!" Nuevamente, tiene un menú desplegable que enumera un puñado de títulos de programas, un medidor de estrellas y un botón Aceptar, y nuevamente el usuario selecciona un programa, selecciona una calificación y luego hace clic en el botón Aceptar.

Ahora, cuando vayamos a Firebase Console, veremos el elemento recién creado.

Una captura de pantalla de Firebase Console. En la columna de la izquierda está joaq-lab con algunas colecciones: asistentes, carreras, calificaciones, pruebas y usuarios. El elemento de calificación está seleccionado y aparece en la columna central con una identificación seleccionada; es el único documento. La columna de la derecha muestra dos campos: "rating" se establece en 4 y "tvShow" se establece en "Mad men".

Finalmente, enumeremos todas las calificaciones en AppComponent . Para hacer eso, en app.component.ts , obtendremos los datos de la colección:

 import { AngularFirestore } from '@angular/fire/firestore'; export class AppComponent implements OnInit { // ... ratings$: Observable<any>; constructor( // ... private af: AngularFirestore ) { } ngOnInit() { // ... this.ratings$ = this.af.collection('ratings').valueChanges(); } }

…y en app.component.html , agregaremos una lista de calificaciones:

 <div class="container"> <div class="row"> // ... <div class="col-6"> <div> <p *ngFor="let rating of ratings$ | async"> {{rating.tvShow}} ({{rating.rating}}) </p> </div> </div> </div> </div>

Así es como se ve nuestra aplicación de tutorial Angular 9 cuando está todo ensamblado.

Una captura de pantalla de una aplicación de tutorial de Angular 9 que muestra un formulario titulado "PROGRAMA DE TV" debajo de un título de página más grande "¡título nuevo!" Nuevamente, tiene un menú desplegable que enumera un puñado de títulos de programas, un medidor de estrellas y un botón Aceptar. Esta vez, una columna de la derecha ya enumera "Mad men (4)", y el usuario califica a Lost con tres estrellas, seguido de "Mad men" nuevamente con cuatro estrellas. La columna de la derecha permanece ordenada alfabéticamente después de las dos nuevas calificaciones.

Angular 9 y Angular Ivy: mejor desarrollo, mejores aplicaciones y mejor compatibilidad

En este tutorial de Angular 9, cubrimos la creación de un formulario básico, el guardado de datos en Firebase y la recuperación de elementos de él.

En el camino, vimos qué mejoras y nuevas funciones se incluyen en Angular 9 y Angular Ivy. Para obtener una lista completa, puede consultar la última publicación del blog oficial de Angular.


Insignia de Google Cloud Partner.

Como Google Cloud Partner, los expertos certificados por Google de Toptal están disponibles para las empresas que lo soliciten para sus proyectos más importantes.