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.
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.
Si ejecutamos ng build --prod
, podemos ver la lista de archivos generados.
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.
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.
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.
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.
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.
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.
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(); } }
Ahora, cuando vayamos a Firebase Console, veremos el elemento recién creado.
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.
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.
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.