🌱 Digital Garden

Search

Search IconIcon to open search

Angular

Last updated Jul 20, 2023 Edit Source

Angular es un Framework UI construido por Google. Caracteristicas

Antes existia un framework llamado AngularJS, creado en 2009 y fue creado utilizando unicamente JavaScript, este recibio una reescritura, la cual fue realizada en TypeScript.

Un Framework combina multiples librerias y provee una interfaz para utilizarlas todas de forma comoda.

Una libreria provee ayuda para realizar operaciones especificas, se puede realizar una combinacion de ellas.

# Caracteristicas

Ademas, ofrece caracteristicas de Server Side Rendering, las cuales soportan SPA.

# Inicializar un Proyecto

Para inicializar un Proyecto de Angular debemos utilizar el siguiente comando, el cual creara un folder con toda la configuracion y una aplicacion por defecto:

1
ng new <app_name>

# Estructura de un Proyecto

La estructura de un proyecto de angular contiene los siguientes archivos.

# tsconfig.json

Se encuentra toda la configuracion de TypeScript por defecto. Ya posee bastante autoconfiguracion y no necesita de muchos cambios.

# tsconfig.spec.json

Incluye configuracion principalmente para testing. Los tests seran generados por el Angular CLI.

# tsconfig.app.json

Es la configuracion final utilizada para compilar

# README.md

Contiene comandos utiles de Angular por defecto y otras herramientas basicas

# package.json

Contiene algunos comandos utiles y las dependencias que se estan utilizando tanto en desarrollo como en compilacion.

# package-lock.json

Es utilizado en equipos de produccion grandes y es el archivo que contiene todas las dependencias reales para ejecutar el proyecto

# karma.conf

Es un corredor de tareas para ejecutar distintas tareas. En Angular los tests se escriben en Jasmin y se ejecutan usando Karma.

# angular.json

Es un archivo de configuracion relacionada al espacio de trabajo actual. Aqui se encuentra toda la configuracion de los proyectos dentro de este espacio de trabajo.

# browserlistrc

Enlista las distintas versiones que seran soportadas por el proyecto en desarrollo.

# src/

Aqui se escribira todo el codigo de la aplicacion.

# test.ts

Es un archivo que no necesita ser tocado, es utilizado para cargar todas las dependencias para que karma.js se configure.

# polyfills.ts

Bastante importante. Usa Polyfills para asegurarse que el codigo se encuentre disponible en versiones anteriores. Ahora mismo, se utiliza la libreria zone.js.

# main.ts

Punto de entrada de la aplicacion de Angular

# index.html

El index.html principal que sera servido a los usuarios. Debido a que angular es SPA

# environments/

Sirve para definir los ambientes en los cuales puede trabajar el proyecto. Angular por defecto crea uno de desarrollo y uno de produccion.

# app/

Aqui reside todo el codigo inicial de la aplicacion. Incluidas las plantillas de HTML, CSS, Routing y muchas otras cosas

# mono-repo

El concepto de mono-repo te permite mantener multiples aplicaciones y librerias dentro de un mismo workspace. Desde aqui, tambien se puede hacer deploy de multiples aplicaciones a la vez desde el mismo repo.

# Inicializacion

En Angular, la gran mayoria de cosas trabaja con Modulos. Los modulos no son mas que clases exportadas para que sean utilizadas como modulos.El punto de entrada de una aplicacion en Angular 15 es llamado AppModule.

Dentro de este modulo tenemos una clase notada con el decorador @NgModule. Dentro de este, se encuentra el import de todos los componentes y otros modulos externos para que se ejecute la aplicacion al punto de lanzamiento.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Este es el llamado Root Module, el cual sera llamado al ejecutar la aplicacion.

# Componente

Es una vista principal que es renderizada al usuario final. Se compone de cuatro cosas:

Una de las partes mas escenciales de un componente es su archivo TS. Este contiene bastantes cosas itneresantes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  title = 'hotelInventoryApp';
}

Como vemos es nuevamente una clase anotada con el decorador @Component, en el decorador, se incluyen metadatos que le dan identidad a este componente. Las funciones de los metadatos son:

Adicionalmente, tambien se nos permite escribir codigo HTML y CSS en la misma linea del componente

1
2
3
4
5
6
7
@Component({
  selector: 'app-root',
  template: '<h1>Hello world from inline HTML</h1>',
  styles: [
	  'h1 { color: red; }',
  ]
})

# Creacion

Para crear un nuevo componente SIEMPRE es recomendao utilizar un prefijo en el selector, esto debido a que HTML puede llegar a implementar la etiqueta de tu aplicacion si no tiene sningun prefijo.

Para configurar el prefijo de los componentes, podemos editarlo en el archivo angular.json del workspace y posteriormente en los distintos componentes.

Para crear un nuevo componente podemos hacer uno de los dos comandos:

1
2
ng generate component <view_name> # Opcion 1
ng g c <view_name> # Opcion 2

# Template Sintax

Como sabemos, las templates son uno de las partes mas importantes debido a que estas sirven para presentar informacion al usuario final. Cada herramienta implementa su propia sintaxis para las plantillas.

# Interpolation

Se te permite interpolar informacion de los tipos de datos basicos desde el archivo TS del componente hacia la plantilla de HTML. Para esto, debemos anotar el nombre de las variables que seran interpoladas en el archivo TS del componente.

1
2
3
// Dentro del componente ...
companyName = 'Registered Company Inc';
// ...

Posteriormente, podemos utilizar la siguiente sintaxis dentro de la plantilla HTML del mismo componente:

1
<h1>Hola. Somos la compañia {{companyName}}</h1>

# Property Binding

Adicionalmente, angular tambien te deja bindear un conjunto de datos a una etiqueta utilizando nombres de variables proporcionadas desde el archivo TS.

1
2
// Dentro del componente ...
roomsCount = 10;

Dentro de la plantilla de HTML, podemos utilizar shorthands dentro de box sintax para bindear informacion a una propiedad de un elemento HTML dinamicamente:

1
<div [innerText]="romsCount"></div> <!-- En Angular -->

Este shorthand seria equivalente al siguiente codigo en JavaScript:

1
document.getElementById('romsCount').innerText = romsCount;

Una cosa a tener en cuenta esque esto solo puede ser utilizado propiedades validas del elemento HTML.

Otro ejemplo de Property Binding:

1
2
3
4
5
6
<h2>Products</h2>
<h3>
<a [title]="product.name + ' details'">
    {{ product.name }}
</a>
</h3>

# Event Binding

En Angular puedes bindear una funcion usando sintaxis de banana de forma rapida.

1
2
3
4
<button (click)="toggle()">Toggle</button>
<div [hidden]="hideRooms">
	<div [innerText]="romsCount"></div> <!-- En Angular -->
</div>

Nuevamente, dentro del archivo TS del componente podemos implementar facilmente las funciones.

1
2
3
4
5
6
7
// Dentro del componente ...
hideRooms = false;
toggle() {
	console.log('El componente ha sido togleado')
	this.hideRooms = !this.hideRooms;
}
// ...

# Directives

Son algo que puede cambiar el comportamiento y la apariencia de un elemento del DOM, son reusables y pueden implementar todo el lifecycle de los hooks. Estos no tienen un template. Son igual a los componentes pero no cuentan con una vista

Algunas de las ya incluidas por Angular son:

1
2
3
4
5
6
*ngIf
*ngFor
*ngSwitch

ngClass
ngStyle

# *ngIf

Es una directiva que funciona como un if. Nos permite mostrar informacion de forma condicional.

1
2
3
<div *ngif="romsCount > 0">
	<p>Rooms List</p>
</div>

Adicionalmente, como alternativa, se nos permite utilizar el Nullish Coalescing Operator:

1
{{ roomsCount > 5 ?? <p>Rooms List</p> }}

Y tambien tenemos opcion de hacer Optional Chaining

1
2
3
<div *ngif="rooms?.count">
	<p>Rooms List</p>
</div>

# *ngFor

Es una directiva que funciona como un for. Nos permite iterar sobre un array de valores de cualquier tipo. Incluidos colecciones de interfaces y objetos.

1
2
3
4
5
6
7
8
9
<!-- Dentro de una tabla-->
<tbody>
	<tr *ngFor="let room in roomsList">
		<td>{{ room.id }}</td>
		<td>{{ room.price }}</td>
		<td>{{ room.number }}</td>
		<td>{{ room.checkin }}</td>
	</tr>
</tbody>

# *ngSwitch

Es una directiva que funciona como un switc. Nos permite seleccionar de forma condicional y por casos un valor de distintas formas.

1
2
3
4
5
<div [ngSwitch]="variable">
	<div *ngSwitchCase="'Case1'">Welcome User!<div>
	<div *ngSwitchCase="'Case2'">Welcome Admin!<div>
	<div *ngSwitchDefault="'Case3'">Welcome Guest!<div>
</div>

# ngClass

Es una directiva que nos permite modificar la clase de CSS de un elemento

1
2
3
<div [ngClass]="variable ?? 'className'">
	<!-- ... -->
</div>

# ngStyle

Lo mismo con ngStyle, se nos permite modificar el inline CSS directamente basado en la evaluacion de una expresion/

1
2
3
<div [ngStyle]="{'color': variable ? 'red' : 'blue'">
	<!-- ... -->
</div>

# Tipos

Para diferenciar entre ellas tenemos una de las confirmaciones mas claras. Las estructurales llevan un asterisco al inicio (como *ngIf o *ngFor) y las de atributo no (como ngClass y ngStyle).

# Estructural

Cambian el comportamiento del DOM, son las mas pesadas y pueden ocasionar problemas de performance. El punto mas importante de estas esque pueden modificar el DOM.

Ejemplos:

# Atribute

Agregan un atributo a un elemento, agregan algo de logica, algunas propiedades pero no se les permite agregar o remover elementos del DOM.

# Pipes

Una Pipe es una forma de transformar los datos dentro de la plantilla. Estas no cambian el objeto en realidad, solo la forma en que este se muestra. A esto ultimo se le llama transformacion. Algunas de las Pipes integradas son:

Adicionalmente, puedes construir las tuyas propias.

Para utilizar una pipe, basta con introducir una pipe dentro de la plantilla de HTML.

1
<td>{{ room.checkinTime | date }} </td>

En este caso, la propiedad ‘checkinTime’ del objeto ‘room’ sera pasada por el pipe ‘date’ lo que la convertira en formato de fecha. Adicionalmente, tambien podemos aplicar un formato personalizado que este disponible en el pipe

1
2
<td>{{ room.checkinTime | date : 'short' }} </td>
<td>{{ room.checkinTime | date : 'dd/MM/yyyy' }} </td>

# Integrar Bootstrap

Para integrar bootstrap existen multiples formas de hacerlo. Una de las formas normales de hacerlo es utilizar el modulo ngx-bootstrap. Para que los cambios surtan efecto de forma satisfactoria necesitamos reiniciar el servidor de angular.

La forma mas facil de instalarlo es utilizar bootstrap

1
ng add ngx-boostrap

Otra forma de instalarlo es mediante npm

1
npm i bootstrap

Posteriormente, copiar el archivo de bootstrap.css y agregarlos a tus estilos globales.

1
@import '~/bootstrap/bootstrap.min.css'

# Integrar cualquier libreria CSS

Para integrar cualquier libreria basta con agregarlo a nuestros estilos globales mediante un importe.

Otra forma de hacerlo es dar el path relativo de los estilos en la configuracion de angular (Archivo angular.json)

1
2
3
4
5
// config
"styles": [
	"src/styles.css",
	"~/path/to/styles.css",
]

# Lifecycle Hooks

Un Lifecycle hook es una cadena de eventos disponibles para un componente. Son un ciclo de vida, desde la creacion hasta la destruccion del ciclo de vida.

La lista del ciclo de vida es:

Para acceder a ellos desde tu componente debes implementar su respectiva interfaz.

1
2
3
class Component implements OnInit {
	// code..
}

Estos Lifecycle hooks existen principalmente para mantener la logica de negocio de tu aplicacion dentro de las distintas etapas de inicializacion de tus componentes. Es decir, estas deberian ser utilizadas en lugar de los constructores.

De hecho, escribir codigo que bloequea en los constructores es una muy mala practica, dentro de estos solo se deberian inyectar servicios u otras dependencias.

# ngOnInit

Es un hook que ocurre justo despues de llamar al constructor. Por tanto, es donde deberia ir todo el codigo que bloquea el thread para su ejecucion, mayormente, logica de negocio.

# ngOnChanges

Este hook se encuentra estrechamente relacionado con la comunicacion entre componentes y el Change Reduction.

El hook del cambio solo se puede usar cuando el componente recibe sus datos mediante @Input. En este hook tenemos disponible un objeto llamado SimpleChanges. Dentro de este tenemos el estado actual, el estado previo y si se trata del primer cambio realizado a este componente.

En caso de utilizar la estrategia OnPush esto solo ocurrira de forma explicita, en caso de no, cuando ocurra cualquier cambio al rededor suyo

# ngDoCheck

Este hook lo que hace esque lanza un evento cada vez que se lanza cualquier evento, sin importar el origen del evento. Es decir, sera un componente reactivo a cualquier cambio que ocurra en el sitio web.

Este hook es extremadamente costoso. No es muy normal implementarlo pero esta disponible para ser utilizado.

Es importante que no se implemente OnChanges y DoCheck en el mismo componente.

# ngAfterViewInit

Este hook se trigerea cuando la vista, es decir, lo que sea que tenga en el template se encuentra inicializado. Eso incluye otros componentes hijos que se encuentren dentro de el.

Es por esto que hasta este punto es que podemos estar seguros que un componente hijo marcado con @ViewChild se encuentra disponible.

Este hook tiene un catch en modo de desarrollo, y esque debido a que el Change Reduction se ejecuta dos veces, editar el contenido ocasiona un error en caso de cambiar el contenido de un componente marcado con @ViewChild.

# ngAfterViewChecked

Es muy muy raro utilizarlo, sin embargo, aqui podemos modificar los componentes de @ViewChild.

# ngAfterContentInit

El contenido es algo que se nos provee por un componente padre. En caso de ser un contenedor y utilizar Content Projection, este evento se trigeara cuando todo el contenido se encuentra inicializado.

El contenido pueden ser incluso otros componentes. Para comunicarse con dichos componentes puedes utilizar @ContentChild.

Este lifecycle hook, por tanto, nos da acceso a estos componentes que tenemos como contenido debido a que cuando se trigeree los componentes significa que han sido cargados.

# ngAfterContentChecked

Este hook tambien es muy raro utilizarlo, algo que se ejecuta solamente una vez cuando tu contenido ha sido completamente checado.

# ngOnDestroy

Cuando el component se remueve del DOM este hook sera llamado. Sirve para cerrar conexiones, desuscribirse de hooks, limpiar session storage, entre otras cosas. Sirve para cerrar y terminar la pagina.

La mayor parte de las veces vas a usar para desuscribirte de datos.

# Content Projection

Es un mecanismo de Angular que nos permite definir el orden de cargado y diseño de la pagina de forma manual.

Para hacer esto podemos aprovechar la etiqueta ng-content. Definimos su atributo “select” y le damos el nombre del componente que queremos que renderice. De esta forma, podemos definir contenedores.

1
2
3
4
<!-- Dentro del Componente Contenedor -->
<ng-content select="name-component"></ng-content>
<ng-content></ng-content>
>ng-content select="section-component"></ng-content>

# ng-content

Es una etiqueta que sirve como placeholder para poder definir el orden en el cual se colocara un elemento.

Desde un componente padre podemos pasar etiquetas de contenido al componente hijo y el componente hijo las recibiria con ng-content

1
2
<!-- Componente Hijo -->
<ng-content></ng-content>

Ahora en el componente padre definimos el contenido des te componente hijo

1
2
3
4
5
<!-- Componente Padre -->
<son-component>
	<!-- Contenido que reemplazara a ng-content -->
	<h2>Hola desde el componente padre estoy reemplazando en el hijo</h2>
</son-component>

# Change Reduction

Es el mecanismo por el cual se actualiza de forma dinamica una pagina basada en los datos. Este es ejecutado dos veces en modo de desarrollo

El comportamiento por defecto de Angular suele causar algunos problemas, por ejemplo, que al modificar el DOM en lugar de que se modifique un componente en particular, se modifican todos.

Para modificar el componente tenemos que configurar su propiedad changeDetection:

1
2
3
4
@Component({
	// ...
	changeDetection: // strategy,
})

Hoy en dia tenemos dos:

Unicamente podemos utilizar onPush si:

  1. Recibe sus datos via comunicacion (@Input y @Ouput o NgRx)
  2. Todos los datos del componente deben ser tratados como inmutables
  3. Un evento se trigeara cuando sus datos son renovados, es decir, son reasignados por un nuevo valor.

# Component Interaction / Communication

La comunicacion entre componentes no es algo muy extraño, se busca lograr que dos componentes mantengan comunicacion entre ellos. Hay multiples formas de lograr esto

# @Input y @Ouput

@Input es un decorador que sirve para recibir datos desde un recurso externo, usualmente, un componente.

1
2
// Dentro de un componente
@Input() rooms: Room[] = [];

Ahora podemos enviar los datos que este recibe desde un template de otro componente o desde cualquier lado, esta informacion es inyectada desde cualquier otro lado!. Unicamente tiene la logica de

1
<component-tag [rooms]="roomList"></component-tag>

En este caso hay dos formas de conocer a esta relacion

A mi en lo personal me gustaria verlo como una notes/Relacion de Asociacion.

@Output es otro decorador, este nos sirve para exportar datos y delegar a otro componente una funcionalidad especifica.

1
2
3
4
5
6
// Componente hijo
@Output() roomSelected = new EventEmitter<Room>();

selectRoom(room: Room) {
	this.roomselected.emit(room);
}

Con el codigo anterior estamos logrando que se envie un evento junto con un objeto de la clase Room a un elemento externo delegando el comportamiento de este de forma efectiva.

De esta forma, podemos recibirlo en el componente padre!

1
<component-tag [rooms]="roomList" (selectRoom)="selectRoom($event)"></component-tag>

Finalmente, podemos especificar el comportamiento con los datos que se nos envia en la variable del evento ‘$event’

1
2
3
4
// Componente padre
selectRoom(room: Room) {
	console.log(room);
}

# @ViewChild

Es otro mecanismo de comunicacion entre componentes. Necesario entenderlo para utilizar ngAfterViewInit Lifecycle Hook.

Usualmente ayuda para integrar un componente de terceros sobre el cual no tienes mucho control. Muchas veces se utiliza cuando el componente que buscas utilizar no tiene @Input y @Output, es decir, no es tan reusable.

En este caso, en lugar de que el componente hijo permita que se accedan y coloquen sus propiedades, el componente padre accedera a ellas de forma forzada.

1
2
// componente padre
@ViewChild(NameComponent) nameComponent: NameComponent;

Mediante este, ahora tenemos una instancia del componente y podemos acceder a cada una de sus propiedades.

Sin embargo, tenemos un problema. Este componente no puede ser utilizado en el lifecycle hook de ngOnInit, a menos que cambiemos una configuracion. Para utilizarlo este solamente puede ser accedido desde ngAfterViewInit.

Si de verdad necesitamos utilizarlo en OnInit necesitamos configurar a que sea estatico:

1
@ViewChild(NameComponent) { static: true } nameComponent: NameComponent;

Si marcamos esta propiedad como estatica estamos indicando que su uso es seguro para el componente padre cuando se encuentre en su hook de @OnInit.

Lo anterior tiene que ver con la asincronidad. Digmos que nuestro componente hijo (NameComponent) tiene codigo asincrono en su hook OnInit, lo que pasaria esque no podriamos estar seguros de que el componente estuviera listo para ser utilizado por su componente padre en su inicializacion hasta que su codigo asincrono se cumpliera.

Es decir, si nosotros estamos seguros que dicho componente hijo no tiene codigo asincrono en su @OnInit entonces es seguro utilizarlo con “static”, esto lo pondra disponible en el @OnInit del componente padre.

En caso de que el componente hijo SI tenga codigo asincrono, unicamente podemos estar seguros de que se encuentra disponible hasta que el componente padre alcance el hook @ViewOnInit.

# @ViewChildren

Una cosa a tener en consideracion esque ViewChild solo accedera al primer elemento que se encuentre de dicho tipo en el template. En caso de querer acceder a todos los elementos debemos usar @ViewChildren en su lugar.

1
@ViewChildren(NameComponent) nameComponents: QueryList<NameComponent>;

# @ContentChild

Es otro decorador, en este caso, nos estamos refiriendo a un componente hijo que se nos es asignado desde un componente padre, y que mediante el Content Projection nosotros le estamos dando un lugar dentro de nosotros mismos.

En este caso, no tenemos acceso estatico, la unica forma de acceder a estos componentes es en el lifecycle hook AfterContentInit.

1
2
3
4
5
6
@ContentChild(EmployeeComponent) employee!: EmployeeComponent;

ngAfterContentInit(): void {
	console.log(this.employee); // Esto funcionara proque estmaos dentro del hook aftercontentinit.
	this.employee.name = 'Jaime';
}

# Servicios

Finalmente, podemos tener multiples componentes compartiendo el mismo servicio, entonces, cada componente puede acceder a los datos, utilizarlos, manipularlos y dejar que otros componentes tambien los usen!.

# Componentes Dinamicos

Para esto, utilizamos ng-template en el componente padre que quiere hacer dinamicidad.

1
2
<!-- Template del componente padre -->
<ng-template #user></ng-template>

Posteriormente, desde el componente padre accedemos a la referencia de ng-template usando @ViewChild y renderizamos un componente a nuestro gusto

1
2
3
4
5
6
7
// Dentro de componente padre
@ViewChild('user', { read: ViewContainerRef }) vcr!: ViewContainerRef;

// Renderizamos un componente OnInit
ngAfterViewInit() {
	const componentRef = this.vcr.createComponent(NameComponent);
}

# ng-template

Es un tag que no renderiza nada, no es visible, pero sirve como placeholder para renderizar otra template, componente, o lo que sea . Es decir, otro componente. Aporta carga dinamica,

# Dependency Injection

En Angular una dependencia puede ser una clase, un servicio, un objeto u otros que puede ser inyectados en un componente u otros servicios.

Es importante entender que este mecanismo solo debe ser utilizado inyectando clases y servicios nunca un componente. Para dichos casos, existe la comunicacion entre componentes.

# Proveedores

Existen distintos tipos de proveedores para que podamos aplicar la Inyeccion de Dependencias en Angular. Los principales son:

# Proveedor Basado en Clases

Para inyectar una clase (servicio u otros) debes de marcarlo con la anotacion @Injectable. Darle su opcion de providedIn para que sea manejado por Angular y expotarlo

1
2
3
4
5
6
7
8
@Injectable({
	providedIn: 'root',
})
export class RoomsService {
	// Logic

	constructor() {}
}

Finalmente, en los componentes donde quieres usar el servicio procedes a inyectarlo en el constructor y utilizarlo normalmente:

1
2
3
4
5
6
@Component(
	// ...
)
export class SomeComponent {
	constructor(private roomsService: RoomsService) {}
}

# Resolucion de Dependencias

Cuando haces inyeccion de dependencias Angular debe tener algun meanismo para saber como resolver las dependencias.

Por defecto, la resolucion de dependencias con ‘root’ llega hasta el componente root de tu aplicacion. En caso de que esta opcion no sea especificada, llegara hasta ‘main.ts’, en caso de llegar ahi y no estar registrado, tirara un error debido a que no hay ningun provider para dicho Injectable.

El mecanismo integrado a partir de Angular 6 fue en la anotacion @Injectable en providedIn se especifica la opcion ‘root’ lo que hace que Angular maneje dicho servicio, lo registre, y lo inyecte.

Por defecto, la inyeccion de dependencias crea un Singleton. Sin embargo, esto puede ser cambiado a que se trabaje por multiples instancias, para ello, dentro de la anotacion @Component de tus componentes puedes agregar la siguiente propiedad de providers:

1
2
3
4
@Component({
	// ...
	providers: [NameService],
})

# Resolution Modifiers

Son modificadores para la resolucion de dependencias. Estos pueden ser especificados en los componentes que utilizan Injectables

1
2
3
// Inside component
constructor(@Self() private nameService: NameService) {}
// More code
1
2
3
// Inside component
constructor(@SkipSelf() private nameService: NameService) {}
// More code
1
2
3
4
5
6
@Component({
	providers: [NameService],
})
// Inside component
constructor(@Host private nameService: NameService) {}
// More code

# Proveedores basados en Valores

En este caso, puedes pasar un objeto como si fuera un servicio. Estos son utiles cuando, por ejemplo, tienes una constante que es utilizada a traves de multiples servicios. Usualmente sirve para proveer servicios que forma la configuracion de la aplicacion.

Veamos el objeto que podremos utilizar como configuracion:

1
2
3
4
// Dentro de appconfig.interface.ts. El objeto que ofreceremos para que se autilizado
export interface AppConfig {
	apiUrl: string,
}

Ahora, convertamoslo a un servicio inyectable

1
2
3
4
5
6
// Dentro de appconfig.service.ts
export const APP_SERVICE_CONFIG = new InjectionToken<AppConfig>('app.config');

export const APP_CONFIG: AppConfig = {
	apiUrl: 'https://localhost:8081/api/v1'
}

Posteriormente tenemos que inyectarlo en los providers de nuestro proyecto. Regularmente para esto, tenemos el fichero app.module.ts, dentro del array “providers” podemos definir el servicio por valor:

1
2
3
4
5
6
7
// Dentro de @NgModule de app.module.ts
providers: [
	{
		provide: APP_SERVICE_CONFIG,
		useValue: APP_CONFIG,
	}
]

Ahora que el servicio se encuentra registrado con un valor y todo dentro de nuestra aplicacion, podemos utilizarlo desde servicios externos como un injectable normal.

1
2
3
4
// Dentro de un servicio o componente
constructor (@Inject(APP_SERVICE_CONFIG) private config: AppConfig) {
	console.log(this.config.apiUrl);
}

Otro caso de uso es utilizar las APIs de Cookie Storage, Local Store, Geolocation, etc. Estas deben ser implementadas usando proveedores por valor.

1
2
3
4
5
6
export const localStorage = new InjectionToken<any>('local_storage', {
	providedIn: 'root',
	factory() {
		return LocalStorage;
	}
})

Gracias a providedIn, este servicio ya se encuentra registrado. Solo queda inyectarlo

1
constructor (@Injyect(localStorage) storage: Storage) {}

# Servicios

En Angular los servicios son una forma de tener piezas de codigo abstraidas reutilizables por multiples componentes.

Un servicio puede ser inicializado, contener todo el codigo reutilizable

La forma ideal de desarrollar una aplicacion es tener el codigo minimo dentro de tus componentes y dividir toda tu logica de negocio en servicios reutilziables que tus componentes puedan utilizar. Los servicios deben contener la mayor parte de tu logica de negocio.

Generalmente estoy servicios son creados dentro de una carpeta especial “services” dentro de cada componente.

Para crear un servicio nuevo podemos utilizar el angular CLI

1
ng generate service <name>

La estructura de un servicio es bastante sencilla.

# name.service.ts

La estructura inicial del archivo TS del servicio es la siguiente:

1
2
3
4
5
6
7
8
@Injectable({
	providedIn: 'root',
})
export class RoomsService {
	// Logic

	constructor() {}
}

Como vemos, tenemos und ecorador que nos indica que este es un servicio inyectable. Ademas tenemos la opcion providedIn.

La opcion providedIn tiene diferentes posibles estados, tenemos root y any. any es utilizada en Routing, root es usualmente utilizada como un servicio normal.

Root permite que Angular haga el registro del servicio, inyecte la dependencia donde sea utilizada y en caso de que no sea usada, el codigo es removido cuando sea desplegado.

# HTTP y Observables

Uno de los temas mas importantes de Angular, debido a que regularmente lo usaremos para consumir APIs.

# HttpClient

Es un servicio que provee Angular para interactuar con APIs, esta basado en RxJs.

Para utilizar HttpClient necesitamos configurar unas cuantas cosas.

Primero, debemos importar el modulo a nuestra aplicacion. Para importarlo, debemos agregar la dependencia en app.module.ts

1
2
3
4
5
6
import { HttpClientModule } from '@angular/common/http'

@NgModule({
	// ...
	imports: [HttpClientModule]
})

Otra cosa a configurar esque necesitamos crear un archivo llamado ‘ proxy.conf.json’. En esta agregaremos las rutas seguras desde las cuales podemos consumir.

Finalmente, en angular.json debemos agregar la configuracion de “proxyConfig”

1
2
3
4
5
//...
"development": {
	"browserTarget": "...",
	"proxyConfig": "src/proxy.conf.json",
}

Una vez tenemos esto configurado, no hay mas problema. Ahora podemos inyectar un cliente http en cualquier servicio

1
constructor (private http: HttpClient) {}

# Peticiones

Y finalmente, gracias a la configuracion del proxy, podemos facilmente hacer llamadas a la API:

1
this.http.get<Room[]>('/api/rooms');

Este mismo objeto http tiene metodos para hacer los distintos tipos de requests

1
2
3
4
this.http.post() // POST
this.http.get() // GET
this.http.put() // put
this.http.delete() // DELETE

Ademas, existe una forma de crear peticiones personalizadas por nuestra cuenta:

1
2
3
4
5
const request = new HttpRequest('GET', 'https://url.com/api', {
	reportProgress: true, //Config
})

return this.http.request(request);

# RxJs y Observables

Angular utiliza RxJs por detras, incluido HttpClient, Routing, Forms y distintas cosas.

RxJs es una libreria que sirve para trabajar con programacion basada en eventos implementando secuencias de observables.

Mediante este, cualquier secuencia de datos que obtenes es un stream de datos.

Este tipo de programacion es llamada Programacion Reactiva.

En el siguiente codigo, creamos un Observable el cual crea un stream, en el stream podemos enviar datos y completarlo.

1
2
3
4
5
6
stream = new Observable(observer => {
	observer.next('user1');
	observer.next('user2');
	observer.next('user3');
	observer.complete();
})

Al enviar datos de esta forma todos los que se encuentren suscritos a este Observable recibiran las actualizaciones.

1
this.stream.subscribe((data) => console.log(data)) // user1, user2, user3.

A la misma funcion de suscribe, podemos pasar hasta tres parametros, para recibir los datos, un error en caso de que aparezca y el estado al ser completado el stream.

1
2
3
4
5
this.stream.subscribe({
	next: (v) => console.log(v),
	complete: (c) => console.log(c),
	error: (e) => console.error(e),
});

# Observable

Recordemos que Observable (Tambien llamado Pub/Sub) es un patron de diseño donde se tiene un elemento el cual es observado por otros objetos, cuando el Observable recibe una actualizacion este notifica a todos los elementos que le observan.

En RxJs estos Observable son streams de datos a los cuales un cliente se puede suscribir.

# Arquitectura Pull

RxJs funciona con una arquitectura de Pull. Es decir, una vez obtienes los datos tienes un stream continuo de datos, en caso de que se agreguen datos al stream, se actualiza de forma automatica sin necesidad de obtener los datos nuevamente.

# Operadores

Existen muchos operadores en RxJs, al rededor de 100+, no los necesitas todos realmente todos para lograr crear aplicaciones.

Aqui veremos algunos de los mas relevantes para aplicarlos en Angular.

# ShareReplay

Te ayuda a cachear la respuesta de la API. De esta forma, los datos pueden ser reutilizados en multiples paginas o en la misma para que cargue rapidamente.

Te permite cachear la peticion. Para implementarla, debemos modificar el stream de datos para utilizar shareReplay:

1
2
3
getRooms$ = this.http.get<RoomListp[]>('/api/rooms').pipe(
	shareReplay(1)
)

La variable terminando con $ significa que este se trata de un stream de datos para que no sea utilizado en otros lugares.

# CatchError

Nos sirve para atrapar errores dentro de un stream,

1
2
3
rooms$ = this.roomsService.getRooms$.pipe(
	catchError((err) => console.error(err))
)

La forma normal de obtener un error e sutilizar un stream al cual se le envian los errores cuando este ocurre

1
2
3
4
5
error$ = new Subject<string>();

rooms$ = this.roomsService.getRooms$.pipe(
	catchError((err) => error$.next(err.message))
)

# Map Operators

Son un conjunto de operadores que sirven para modificar los datos del stream

1
2
3
roomsCount$ = this.roomsService.getRooms$.pipe(
	map((el) => el.length)
)

# HttpInterceptors

Es un servicio el cual se va a colocar entre las llamadas HTTP y la parte del cliente. De esta forma, puede controlar las peticiones que se envian y se reciben.

Para generarlo, debemos utilizar el Angular CLI:

1
ng g interceptor <name>

Antes de utilizarlo, debemos registrarlo en los providers de app.module.ts:

1
2
3
4
5
6
7
8
// ....
providers: [
	{
		provide: HTTP_INTERCEPTORS,
		useClass: NameInterceptor,
		multi: true,
	}
]

Ahora en su metodo ‘intercept’ podemos clonar el request y enviarla modificada con lo que nosotros queramos, como por ejemplo, unos headers

1
2
3
4
5
6
7
8
9
@Injectable()
export class NameInterceptor implements HttpInterceptor {
	constructor() {}

	intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
		const updatedRequest = request.clone({ headers: new HttpHeaders({ 'Token': "123sadsometoken123"})});
		next.handle(updatedRequest);
	}
}

# APP_INITIALIZER

Es un poco avanzado, pero es bastante util. Este es un servicio que ayuda a cargar los datos antes de que la aplicacion sea inicializada.

Digamos que tenemos que cargar una configuracion antes de que inicie la aplicacion, este es uno de los casos mas utiles cuando se utiliza.

Para hacerlo, primero debemos crear un servicio.

1
ng g s <name>

Posteriormente, crear una funcion dentro de app.module.ts y le inyectamos el servicio. Despues, retornamos una funcion la cual sera la funcion dentro del servicio que contiene todo el codigo a ejecutar en inicializacion.

1
2
3
function functionName(initNameService: initNameService) {
	return () => initNameService.init();
}

Finalmente, lo registramos en providers

1
2
3
4
5
6
7
8
// ...
providers: [
	{
		provide: APP_ INITIALIZER,
		useFactory: functionName,
		deps: [InitNameService]
	}
]

# Routing

Una Route es una forma de renderizar partes de la pagina de forma dinamica, es decir, en vez de ir a pedir una nueva pagina al servidor, el Router se encarga de remover y agregar a la pantalla un componente especifico.

De esta forma, podemos tener funcionalidad de SPA debido a que no necesitas cargar nuevas paginas, simplemente se cargan las partes conforme se vaya necesitando. Todo esto pasa en el frontend.

Para tener routing, se utiliza de un modulo de Angular llamado AppRoutingModule. En este, se deben importar los componentes que seran las diferentes rutas en tu aplicacion. Este modulo puede ser generado automaticamente por el CLI al momento de crear la aplicacion si se le indica que se utilizara Routing.

# Definir una Route

Para definir una Route se tienen que seguir los siguientes pasos:

  1. Importar el AppRoutingModule hacia el AppModule y agregar al “imports” array. Esto lo realiza el CLI si se le indica al inicio, si no, se tendra que hacer manualmente.
  2. Importar el RouterModule y Routes desde @angular/core, definir un array de Routes y agregar el RouterModule a los imports (aqui se especifican las rutas a utilizar en su propiedad forRoot, en este caso, el array de Routes definido antes) y exports de AppRoutingModule. Esto lo realiza el CLI por defecto.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; // CLI imports router

const routes: Routes = []; // sets up routes constant where you define your routes

// configures NgModule imports and exports
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
  1. Definir tus rutas dentro del array de Routes. Cada Route es un objeto de JavaScript que cuenta con dos partes. La propiedad “path” define la ruta en el navegador y la propiedad “component” que define el componente que se renderizara en dicha ruta.
1
2
3
4
const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component', component: SecondComponent },
];

Estas rutas deben definirse de mas especificas a menos especificas.

  1. Inserta tus nuevas rutas en tu aplicacion, para esto, dentro deltemplate de un componente se debe de designar etiquetas Anchor (<a>) con el atributo routerLink el cual lleva el “path” del componente que quieres mostrar. Finalmente define el elemento <router-outlet> que es donde se va a mostrar los componentes mediante routing
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<h1>Angular Router App</h1>
<!-- This nav gives you links to click, which tells the router which route to use (defined in the routes constant in  AppRoutingModule) -->
<nav>
  <ul>
    <li><a routerLink="/first-component" routerLinkActive="active" ariaCurrentWhenActive="page">First Component</a></li> <!-- Ruta 1. -->
    <li><a routerLink="/second-component" routerLinkActive="active" ariaCurrentWhenActive="page">Second Component</a></li> <!-- Ruta 2. -->
  </ul>
</nav>
<!-- The routed views render in the <router-outlet>-->
<router-outlet></router-outlet>

RouterLinkActive sirve para definir clases de CSS para cuandose enecuentre activo dicho route.

Es una parte un poco avanzada, sirve para enviar parametros a traves de Routes dinamicas en la aplicacion. Es util cuando por ejemplo queremos renderizar diferentes contenido en la misma template pasando parametros a la URL.

Para utilizarlo, debemos definir los parametros en las routes:

1
2
3
const routes = [
	{ path: 'items/:id', component: ItemComponent },
]

Posteriormente, desde un anchor que envie a este link podemos enviarlo:

1
<a [routerLink]="/items, item.id">Click me</a>

En caso de que queramos acceder a los datos enviados debemos utilizar ActivatedRoute Service y acceder a su propiedad “params” y leerla con RxJs

1
2
3
4
5
6
// Dentro de un componente
id$ = this.activatedRoute.params.pipe(
	map(params => params[id]);
)

constructor (private activatedRoute: ActivatedRoute) {}

Estos te dan pares de clave valor con los parametros que se pasan a traves del URL.

Existe otra alternativa, podemos utilizar paramMap que tambien interactua mediante RxJs, sin embargo, en este podemos utilizar metodos propios de un mapa para poder acceder a los valores:

1
2
3
id$ = this.activatedRoute.paramMap.pipe(
	map(params => params.get('id'));
)

Mas informacion en la documentacion oficial

# Redireccionamiento Programatico

Adicionalmente, tambien podemos redireccionar de forma programatica haciendo uso del Router. Para ello, primero lo inyecatamos dentro de nuestro componente

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export class LoginComponent {
	email: string = '';
	password: string = '';

	constructor(private route: Router) {}
	login() {
		// some logic
		this.route.navigate(['/path', 'second'])
		this.route.navigateByUrl('/rooms/add')
	}
}

# ActivatedRoute Service

Es un servicio que nos provee Angular Router, nos sirve para interactuar mas afondo con el Routing.

Para utilizarlo basta con inyectarlo a cualquier componente que queramos.

1
2
// Dentro de un componente
constructor (private activatedRoute: ActivatedRoute) {}

# Angular Material

Es una libreria de componentes UI que utiliza MaterialCSS. Es creada y mantenida tambien por Google y provee muchos componentes para facilitar el edsarrollo.

Para agregarla basta con ejecutar el comando en el CLI

1
ng add @angular/material

# Schematics

Angular Material provee schematics que son como templates las cuales ya tienen implementadas gran parte de una funcionalidad que nos interesa.

Mediante estos, podemos generar una barra de navegacion por ejemplo:

1
ng generate @angular/material:navigation <nombre>

Esto nos creara un componente con una barra de navegacion implementada

# Template Driven Forms

Es un tipo de formularios que provee Angular. Mediante estos podemos intercambiar informacion con el usuario.

En estos, se crean los formuarios utilizando etiquetas en HTML, ademas, se utiliza ngModel para tener two way data binding. Es recomendado si se trata de formularios simples y pequeños

# Setup

Lo primero que hay que hacer es importar FormsModule a AppModule

1
2
// Dentro de AppModule
imports: [FormsModule],

# Two Way Databinding

Los formularios en Angular proveen soporte para Two Way Databinding, lo que tenemos que hacer primero es definir dentro del codigo ts del formulario un modelo que servira como base para mantener la informacion entre los dos actualizada.

1
2
// Dentro del TS del componente room-form
roomData!: Room;

Posteriormente, debemos utilizar ngModel dentro del HTML para bindear los datos:

1
<input class="form-control" type="text" name="roomType" [(ngModel)]="roomData.roomType">

Ahora podemos proceder a verificar que todo funcione dinamicamente:

1
{{ roomData | json }}

Finalmente, podemos especificar dentro del formulario un ngSumbit para que ejecute una funcion para enviar datos al backend:

1
2
3
<form (ngSumbit)="addRoom()">
	<!-- ... -->
</form>

Debido al two-way databinding ahora podemos enviar los datos accediendo al modelo sin problemas

1
2
3
4
5
6
// Dentro del componente
addRoom() {
	console.log('Sending data...');
	console.log(this.roomsData);
	console.log('Data sent!');
}

# Validacion

La validacion en este tipo de formularios se hace utilizando HTML5.

# Atributos de Validacion

Algunos de los atributos de alidacion relevantes son:

# Por campo

Para presentar los errores de uno de los campos podemos bindear el input en particular con un modelo e imprimir por pantalla:

1
2
3
4
5
<input #roomType="ngModel" class="form-control" type="text" name="roomType" [(ngModel)]="roomData.roomType">

<div class="errors">
	<p> {{ roomType.errors | json }} </p>
</div>

# Todo el formulario

Por otro lado, podemos acceder a todo el formulario y conocer si este es completamente valido o invalido:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<p>{{ roomsForm.pristine }} </p>
<p>{{ roomsForm.dirty }} </p>


<p>{{ roomsForm.valid }} </p>
<p>{{ roomsForm.invalid }} </p>

<p>{{ roomsForm.value }} </p>


<form #roomsForm="ngForm" ngSubmit="addRoom()">
</form>

# Restear formulario

Para reiniciar el formulario despues de una accion (como en submit) podemos utilizar los metodos especiales de un NgForm “reset” y “resetform”.

Para ello, primero enviamos el ngForm actual mediante una llamada a la funcion:

1
2
<form #roomsForm="ngForm" ngSubmit="addRoom(roomsForm)">
</form>

Posteriormente, utilizamos los metodos disponibles en NgForm:

1
2
3
4
addRoom(roomsForm: NgForm) {
	roomsForm.reset() // Hara que el formulario elimine todo y quede en un estado limpio
	roomsForm.resetForm(obj) // Indicara que dentro del objeto enviado se encuentran los valores iniciales que deben tener los campos del formulario.
}

# Custom Directive

Podemos crear una directiva custom. Este tiene muchos use cases. Para el ejemplo, veamos como crear una directiva “hover” que hara cierta interaccion basada en el hover del usuario.

Para inicializar una directiva utilizamos el CLI

1
ng g d <name>

La estructura de una directiva es bastante similar a un servicio pero con Lifecycle Hooks

1
2
3
4
5
6
@Directive({
	selector: '[hinvHover]',
})
export class HoverDirective {
	constructor() {}
}

Para acceder al elemento sobre el cual se esta aplicando la directiva podemos inyectarlo en el constructor:

1
constructor(private element: ElementRef) {}

Y para utilizarla, ocupamos el nombre de la directiva definida en su “selector” propiedad:

1
<p hinvHover>Texto</p>

A partir de aqui podemos proceder a modificarla!

1
2
3
4
5
6
7
8
9
@Directive({
	selector: '[hinvHover]',
})
export class HoverDirective implements OnInit {
	constructor(private element: ElementRef) {}
	ngOnInit() {
		this.element.nativeElement.style.backgroundColor = 'red';
	}
}

# Basada en Eventos

Tambien podemos aplicar una directiva basada en eventos que ocurran con el elemento en cuestion. Para esto, tomamos ayuda de la anotacion @HostListener, esta recibe un evento y trigerea la funcion especificada

1
2
3
4
5
6
7
@HostListener('mouseenter') onMouseEnter() {
	this.element.nativeElement.style.backgroundColor = 'green';
}

@HostListener('mouseleave') onMouseLeave() {
	this.element.nativeElement.style.backgroundColor = 'red';
}

# Custom Validation

Podemos aprovechar las Custom Directives para hacer validacion personalizada, es decir, podemos agregar logica en codigo!

Partiendo de que ya tenemos una directiva creada, debemos implementar la interfaz “Validator” para que sea un validador efectivo. Ademas, debemos definir en sus providers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Directive({
	selector: '[hinvEmailValidator]',
	providers: [
		{
			provide: NG_VALIDATORS,
			useExisting: EmailValidatorDirective,
			multi: true,
		}
	]
})
export class EmailValidatorDirective implements Validator {
	// ...

	validate(control: AbstractControl): ValidationErrors | null {
	}
}

En la parte de providers se esta registrando la directiva actual como un validator, extendiendo la funcionalidad de angular.

Posteriormente, se esta implementando la interfaz Validator que le muestra a Angular que la clase actual es un validador

Finalmente se implementa el metodo de la interfaz en el cual se contendra la logica necesaria para que la funcion se lleve acabo

Dentro del metodo tenemos unas cuantas cosas, primero, el parametro control de la clase AbstractControl te da acceso al input del formulario y sus contenidos.

Posteriormente, el retorno de ValidationErrors se trata de un objeto de pares de clave-valor en el cual se especifican los errores que tiene el campo en particular.

1
2
3
4
5
6
7
8
validate(control: AbstractControl): ValidationErrors | null {
	const value = control.value as string;
	if (!value.contains('@')) {
		return {
			invalidEmail: true,
		}
	}
}

# Feature Module

Es una buena practica dentro de Angular que consiste en acoplar todos los componentes de funcionalidades muy estrechas para que se vuelva una pieza reutilizable.

Esto te permite a futuro implementar Lazy Loading sin muchos problemas. Ademas, te permite configurar Routing a nivel de modulo aprovechando forChild.

Es bastante utilizado en aplicaciones de gran escala, en caso de no estar implementado, puede generar bastantes problemas.

# Setup

Para crear un modulo podemos utilizar el comando del CLI. Ademas, podemos agregarle routing:

1
ng g m <name> --routing --flat=true

Posteriormente, debemos registrar todos los componentes y funcionalidades hacia este modulo. Funciona de forma bastante igual al app.module original. Por tanto, debemos registrar en las declaraciones y los import las dependencias necesarias.

Finalmente, debemos importar este nuevo modulo en el apps.module original

1
2
// Dentro de apps.module @NgModule decorador
imports: [NameModule],

Para esto, debemos entender que un modulo puede encontrarse importado en muchos otros modulos, sin embargo, un componente solo puede pertenecer a un modulo.

Por tanto, si queremos utilizar un mismo componente en multiples lugares necesitamos añadirlo a un modulo y que este modulo posteriormente lo exponga dentro de su array “exports” de la anotacion @NgModule.

Finalmente, si queremos agregar routing, en vez de utilizar el metodo .forRoot del RouterModule utilizamos .forChild debido a que queremos agregar las rutas al modulo actual. Solo debemos tener cuidado con la definicion de los importes. Estos siempre deben encontrarse sobre AppRoutingModule Esto es debido a que estos son importados de arriba hacia abajo y al igual que el routing, se ejecutan de mas especifico a menos especifico.

1
2
3
4
5
// app-module
imports: [
	FeatureModule,
	AppRoutingModule,
]

# Nested Route y Child Route

Nested Routes son Routes que son cargadas dentro de otras routes. Estas a suez, pueden tener child routes.

Esto nos sirve para cuando por ejemplo queremos mostrar el contenido del router-outlet en la misma pagina donde se carga originalmente.

Para esto, necesitamos definir la Nested Route dentro del modulo donde queremos que aparezca. Para esto utilizamos su propiedad del “children” la cual es un array de rutas.

1
2
3
4
5
6
7
8
9
const routes: Routes = [
	{
		path: 'rooms',
		component: RoomsComponent,
		children: [
			{ path: 'nested', component: RoomsInfoComponent }
		]
	}
]

# Lazy Loading

Es otra propiedad que se nos proporciona gracias a los modulos. Mediante estos, podemos hacer Lazy Loading, es decir, unicamente descargar ciertos archivos necesarios para que partes de la pagina funcionen en demanda. Esto es bueno debido a que no siempre un usuario va querer acceder a todas las funcionalidade de la pagina, enviar todo el codigo seria un desperdicio.

Un Lazy Module debe de cumplir con las siguientes caracteristicas:

Es decir que un Lazy Module debe de ser un elemento completamente autosuficiente en su funcionamiento.

  1. Ningun otro modulo deberia conocer la existencia de tu Lazy Module
  2. Importar el routing del lazy module desde el module principal. Para lograr esto definimos el module como un path normal dentro de routes pero le indicamos que para que funcione primero tiene que cargas las rutaschildren de dicho modulo.
1
2
3
4
5
6
const routes: Routes = [
	{ 
		path: 'rooms',
		loadChildren: () => import('./rooms/rooms.module').then((m) => m.RoomsModule),
	}
]

Esto debe ser implementado de acuerdo a las necesidades del negocio, regularmente, en cosas que los usuarios no suelen modificar mucho.

# Configuracion desde el CLI

Adicionalmente, todo esto puede ser automatizado utilizando el CLI. Para ello, debemos crear un modulo:

1
ng g m <lazy_name> --route=<lazy_name> --routing --module=<name>

La opcion route especifica el path que tendra este modulo

La opcion module define el modulo donde sera creada la route para lazyloading.

# Provider Types

La inyeccion de dependencias tiene dos formas de usar providedIn dentro de sus servicios:

1
2
3
4
@Injectable({
	providedIn: 'any',
})
// ...

# Route Guards

Son guardias para asegurar las Routes. Las disponibles son:

# Setup

Para crear un Guard podemos utilizar la CLI, tambien nos dara un scaffolding inicial para poder crearla:

1
ng g g <name>

Esto te brindara un menu donde puedes elegir cuales Guards quieres implementar

Posteriormente, dentro tu configuracion del routing debes definir los guards que aplicaran a los distintos path:

1
2
3
const routes: Routes = [
	{ path: 'rooms', canActivate: [NameGuard], resolve: {property: PropertyGuard}} // Se define el guard canActivate y se le asigna el guard "NameGuard"
]

Finalmente, dentro del guard queda definir la logica de negocio sobre la cual la guard permitira el paso o no. Para ello, usualmente se utiliza un servicio para que viva toda la logica de negocio y este desacoplado de los guards:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Injectable({
	providedIn: 'root'
})
export class LoginGuard implements CanActivate {
	constructor(private login: LoginService) {}

	canActivate(
		route: ActivatedRouteSnapshot,
		state: RouterStateSnapshopt): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean {
			return this.login.isLoggedIn;
		}
	
}

# Route Events

El mecanismo de Routing tambien tiene un ciclo de vida, desde que se da click para dirigirse a otra ruta hay una serie de eventos que ocurren hasta llegar a su culminacion. Entre ellos, tenemos algunos como las pruebas de los Guards, retribucion de informacion, inicializacion, fin, entre otros.

# Reactive Forms

Aqui en vez de utilizar HTML para crear el formulario se utiliza TypeScript para construirlo completamente. Es bastante bueno si buscas tener mas control via TS.

Las APIs que se utilizan son iguales, solo que aqui se aplica un poco mas el uso de ellas con FormControl FormGroup, directivas de Forms, entre otros.

# Setup

Lo primero que hay que hacer es importar el ReactiveFormModule al modulo que queremos que tenga el form reactivo. dentro de sus imports.

# Form Reactivo

Para crear un Form, la forma mas basica es utilizar la clase FormGroup. Esta representa un formulario completo de multiples campos (Es decir, compuesto de multiples FormControl)

1
bookingForm!: FormGroup;

Otra clase bastante importante es el FormBuilder, mediante este podemos crear Forms complejos. Podemos pasar un objeto con controls basicos:

1
2
3
4
5
6
7
8
9
constructor(private fb: FormBuilder) {}

ngOnInit() {
	this.bookingForm = this.fb.group({
		roomId: [''], // Shorthand del de abajo. No tan recomendado
		name: new FormControl(''),
		// other form controls
	})
}

Finalmente, para renderizarlo en la template, debemos utilizar directive binding llamada formGroup y dentro de ella, los distintos formControl:

1
2
3
4
<form [formGroup]="bookingForm">
	<input formControlName="roomId" type="number">
	<input type="text" formControlName="name">
</form>

# Submit

Submit es otro tema importante de los forms. Para esto, utilizamos el mismo approach de los Template Driven Forms. Se utiliza el bindeo del evento ngSubmit

1
2
3
<form [formGroup]="bookingForm" (ngSubmit)="addBooking()">
	<!-- ... -->
</form>

Y se agrega un metodo para lidiar con el submit:

1
2
3
addBooking() {
	console.log(this.bookingForm.value);
}

# Disabled

Ademas, podemos definir un campo de un formulario como disabled, esto quiere decir que no se encontrara disponible para la edicion del usuario

1
2
3
4
5
6
ngOnInit() {
	this.bookingForm = this.fb.group({
		name: new FormControl({value: '', disabled=true}), // Este FormControl no estara disponible para la edicion en el formulario
		// other form controls
	})
}

El catch aqui esque estos valores que se encuentren disabled no se encontraran disponibles en la propiedad .value del resultado. Para acceder a ellos debemos utilizar un metodo llamado getRawValue del FormGroup.

1
2
3
addBooking() {
	console.log(this.bookingForm.getRawValue());
}

# Form Nested

Ademas, se te permite crear un FormGroup dentro de otro FormGroup.

Para hacer esto, basta con agregarlo mediante el formBuilder:

1
2
3
4
5
6
7
8
9
ngOnInit() {
	this.bookingForm = this.fb.group({
		name: new FormControl({value: '', disabled=true}), 
		addressForm: this.fb.group({
			street: new FormControl(''),
			zipCode: new FormControl(''),	
		}),
	})
}

Para acceder a este desde el HTML tambien tenemos que hacerlo de forma anidada debido a que se encuentra uno dentro de otro, para ello, utilizamos el binding de directivas con formGroupName y seguimos usando formControlName:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- Form Inicial -->
<form [formGroup]="bookingForm">
	<input formControlName="roomId" type="number">
	<input type="text" formControlName="name">
	<!-- Form Anidado -->
	<div [formGroupName]="addressForm">
		<input formControlName="street" type="text">
		<input formControlName="zipCode" type="number">
	</div>
</form>

# Agregar FormControl de forma Dinamica

Ademas de la definicion de los FormGroup al crearlos con FormBuilder, tambien podemos agregar FormControl a un FormGroup de forma dinamica, es decir, podemos construir un CMS!.

Para esto podemos aprovechar de un metodo interesante de FormBuilder para crear un array de FormGroups:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ngOnInit() {
	this.bookingForm = this.fb.group({
		name: new FormControl({value: '', disabled=true}), 
		addressForm: this.fb.group({
			street: new FormControl(''),
			zipCode: new FormControl(''),	
		}),
		guests: this.fb.array([]),
	})
}

addGuest() {
	this.guests.push(this.fb.group({
		guestName: new FormControl(''),
		age: new FormControl(''),
	}));
}

Ahora podemos bindear un boton que mediante event binding llame al addGuest(), el addGuest agregara un FormGroup a el array.

Para finalizar, tenemos tambien que mostrarlo en la plantilla, para ello, podemos utilizar property binding con formArrayName:

1
2
3
4
5
6
7
8
9
<!-- Dentro del formulario bookingForm -->
<div formArrayName="guests">
	<div *ngFor="let guestForm of guests.controls; let i = index">
		<div [formGroupName]="i">
			<input type="text" formControlName="guestName">
			<input type="number" formControlName="age">
		</div>
	</div>
</div>

# addControl y removeControl

Son dos metodos utiles en el FormBuilder que nos sirve para agregar y remover formcontrols de forma dinamica.

1
2
3
addPassport() {
	this.bookingForm.addControl('passportId', new FormControl(''));
}

Y finalmente podemos usar las directivas como *ngIf para renderizarlo en el template

1
2
3
<div *ngIf="bookingForm.get('passportId')">
	<input type="number" formControlName="passportId">
</div>

# Validacion

Asi como los Template Driven Forms, en los Forms Reactivos usualmente tambien busamos tener alguna especie de validacion. Para ellos, dentro del constructor FormControl podemos recifir un objeto el cual porta todos los validators para dicho campo.

1
2
3
4
this.bookingForm = this.fb.group({
	roomId: new FormControl({ value: '2', disabled: true }, validators: [Validators.required]),
	name: new FormControl({ value: ''}, { validators: [Validator.minLength(5)]}),
})

En este caso se utiliza “Validators” el cual es una coleccion de funciones que pueden ser aplucadas a dicho FormControl.

# Mostrar mensajes de Validacion

Ademas, regularmente siempre se busca que los usuarios conozcan los errores en sus datos. Para dichos casos podemos utilizar el metodo hasError con el nombre del tipo de error de validacion. De esta forma podemos saber si un campo tiene errores. Posteriormente, podemos obtener los errores

1
2
3
<div *ngIf="bookingForm.get('name')?.hasError('minlength')">
	<p>El nombre debe ser minimo de X caracteres </p>
</div>

# Custom Validacion

Al igual que con los template-driven forms, podemos construir nuestra custom validation tanto a nivel FormControl como a nivel FormGroup.

# Validacion de FormControl

Para ello, podemos seguir las convenciones que se utilizan en Validators.

1
2
3
4
5
6
7
8
9
export class CustomValidators {
	static validateName(control: AbstractControl) {
		const value = control.value as string;
		// validacion.
		return {
			invalidName: true;
		}
	}
}

Posteriormente lo puedes agregar al array de validaciones en el constructor del form control

1
guestName: new FormControl({ value: '' }, {validators: [Validators.required, CustomValidaros.validateName]})

Un validator mas complejo con logica y parametros debe de retornar una funcion que reciba el control como unico parametro. Para realizar una funcion asi aprovechamos el mecanismo de closures.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Dentro de CustomValidators
static doesNotContainChar(char: string) {
	return (control: AbstractControl) => {
		const value = control.value as string;
		if (value.contains(char)) {
			return {
				containsInvalidChar: true;
			}
		}
		return null;
	}
}
# Validacion de FormGroup (Form entero)

Este es un tipo de validacion bastante util que sirve cuando quieres validar los valores de dos campos entre ellos.

Por ejemplo. imaginemos que tenemos una fecha de inicio y una fecha de fin, obviamente la fecha de fin no debe estar antes de la fecha de inicio. En dicho caso, hariamos uso de este tipo de validacion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
static validateDates(control: FormGroup) {
	const start = control.get('startDate')?.value;
	const end = control.get('endDate')?.value;

	if (end < start) {
		return {
			start?.setErrors({
				invalidDates: true,
			})
			invalidDates: true,
		};
	}
	return null;
}

Este validator se agrega en el mismo objeto del FormGroup en el cual podemos indicar cuando ocurren los eventos de actualizacion (“updateOn”)

1
bookingStatus: new FormControl({ value: '', required: true }, { updateOn: 'blur', validators: [CustomValidators.validateDates]})

# Eventos de interaccion con el usuario

Gran parte de la reactividad proviene de esta funcionalidad particular, mediante este, podemos obtener un stream de datos en el cual se nos va actualizando conforme el usuario va interactuando con loos distintos campos del formulario. Todo esto en tiempo real.

Para utilizar esta API nuevamente usamos un metodo del FormGroup.

1
2
3
this.bookingForm.valueChanges.subscribe((data) =< {
	console.log(data); // Stream de eventos que ocurren en el formulario
})

Mediante este tienes actualizaciones en tiempo real de cualquier keypress que ocurra dentro del formulario. Sin embargo, esto puede ser manipulado para que solo envie ciertos eventos, para esto tenemos la propiedad updaetOn del constructor de formControl en su objeto de validacion.

1
bookingStatus: new FormControl({ value: '', required: true }, { updateOn: 'blur', validators: [Validators.required]})

Adicionalmente, en la creacion de un FormGroup este tambien puede ser enviado

# Operadores de RxJs Utiles (mergeMap, switchMap y exhaustMap)

Son tres operadores que pueden ser muy uiles cuando se trabaja con los eventos de interaccion con el usuario

# Custom Pipe

Adicionalmente, tambien podemos construir un custom pipe. Para inicializarlo podemos utilizar el CLI:

1
ng g p <name>

Esto te genera el scaffolding inicial de una pipe. A esta le tienes que agregar parametros, tipo de retorno y la logica a aplicar, debido a que lo que genera el CLI es bastante general.

Finalmente, para utilizarla, basta con agregarla en tu HTML como cualquier otro pipe

# Global Error Handling

Es un servicio que provee angular para lidiar con errores. Esta clase puede ser implementada para tus propositos del proyecto.

1
2
3
4
5
export class GlobalErrorHandler implements ErrorHandler {
	handleError(error: any): void {
		
	}
}

Posteriormente se registra en el modulo app dentro de los providers, es un provider mas pero este es registrado como error handler

1
2
// Dentro de providers[] array
{ provide: ErrorHandler, useClass: GlobalErrorHandler },

Finalmente, podemos lidiar con errores utilizando esta clase.