Angular
Angular es un Framework UI construido por Google. Caracteristicas
- Basado en Componentes para construir aplicaciones escalables.
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
- Templates
- Data Binding
- Forms
- Routing. Mecanismo bastante util que ayuda a construir notes/Single Page Application.
- Observables
- PWAs
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:
| |
# 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.
| |
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:
- app.component.ts. configuracion en TS
- app.component.spec.ts. Unit Testing
- app.component.css. Estilos
- app.component.html. Estructura
Una de las partes mas escenciales de un componente es su archivo TS. Este contiene bastantes cosas itneresantes:
| |
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:
- selector. El nombre que adquirira este componente y que podra ser reutilizado dentro de una plantilla de HTML mas adelante.
- templateUrl. La localizacion de la plantilla que representa este componente.
- styleUrls. La localizacion de los archivos de estilo que usara este componente.
Adicionalmente, tambien se nos permite escribir codigo HTML y CSS en la misma linea del componente
| |
# 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:
| |
# 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.
| |
Posteriormente, podemos utilizar la siguiente sintaxis dentro de la plantilla HTML del mismo componente:
| |
# Property Binding
Adicionalmente, angular tambien te deja bindear un conjunto de datos a una etiqueta utilizando nombres de variables proporcionadas desde el archivo TS.
| |
Dentro de la plantilla de HTML, podemos utilizar shorthands dentro de box sintax para bindear informacion a una propiedad de un elemento HTML dinamicamente:
| |
Este shorthand seria equivalente al siguiente codigo en JavaScript:
| |
Una cosa a tener en cuenta esque esto solo puede ser utilizado propiedades validas del elemento HTML.
Otro ejemplo de Property Binding:
| |
# Event Binding
En Angular puedes bindear una funcion usando sintaxis de banana de forma rapida.
| |
Nuevamente, dentro del archivo TS del componente podemos implementar facilmente las funciones.
| |
# 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:
| |
# *ngIf
Es una directiva que funciona como un if. Nos permite mostrar informacion de forma condicional.
| |
Adicionalmente, como alternativa, se nos permite utilizar el Nullish Coalescing Operator:
| |
Y tambien tenemos opcion de hacer Optional Chaining
| |
# *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.
| |
# *ngSwitch
Es una directiva que funciona como un switc. Nos permite seleccionar de forma condicional y por casos un valor de distintas formas.
| |
# ngClass
Es una directiva que nos permite modificar la clase de CSS de un elemento
| |
# ngStyle
Lo mismo con ngStyle, se nos permite modificar el inline CSS directamente basado en la evaluacion de una expresion/
| |
# 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:
- *ngIf
- *ngFor
- *ngSwitch
# 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:
- DatePipe. Pasa una fecha a una representacion exacta
- UpperCasePipe. Pasa una cadena de texto a mayusculas
- LowerCasePipe. Pasa una cadena de texto a minusculas
- CurrencyPipe. Te permite agregar un simbolo de moneda en un numero
- DecimalPipe. Te permite pasar un numero a decimal
- PercentPipe. Te permite pasar un numero a porcentaje. Este debe ser decimal
- JsonPipe. Te permite mostrar una representacion JSON de un elemento
- SlicePipe. Te sirve para hacer un slice de un array de elementos
- AsyncPipe. Al trabajar con HttpRequest, muchas veces utilizamos subscribe en todos lados, siempre que nos suscribimos por buena practica debemos desuscribirnos del stream. Esta pipe nos permite manejar esta misma suscripcion de forma automatica. Para que funcione, debemos de pasarle un stream de datos. Evita a toda costa usar multiples async sobre el mismo stream, en su lugar, dale un nombre utilizando “as —-”.
Adicionalmente, puedes construir las tuyas propias.
Para utilizar una pipe, basta con introducir una pipe dentro de la plantilla de HTML.
| |
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
| |
# 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
| |
Otra forma de instalarlo es mediante npm
| |
Posteriormente, copiar el archivo de bootstrap.css y agregarlos a tus estilos globales.
| |
# 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)
| |
# 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:
- ngOnChanges. Es una forma de hacer Component Communication
- ngOnInit. Similar al constructor, es en la inicializacion del componente
- ngDoCheck.
- ngAfterContentInit
- ngAfterContentChecked
- ngAfterViewInit
- ngAfterViewChecked
- ngOnDestroy
Para acceder a ellos desde tu componente debes implementar su respectiva interfaz.
| |
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.
| |
# 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
| |
Ahora en el componente padre definimos el contenido des te componente hijo
| |
# 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:
| |
Hoy en dia tenemos dos:
- default. Cualquier cambio hara que se actualice todo de lugar
- onpush. Unicamente recibira los cambios cuando sus datos sean renovados,
Unicamente podemos utilizar onPush si:
- Recibe sus datos via comunicacion (@Input y @Ouput o NgRx)
- Todos los datos del componente deben ser tratados como inmutables
- 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 @Output
- @ViewChild y @ContentChild
- Servicios
# @Input y @Ouput
@Input es un decorador que sirve para recibir datos desde un recurso externo, usualmente, un componente.
| |
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
| |
En este caso hay dos formas de conocer a esta relacion
- Smart Component / Dumb Component
- Parent / Child Component
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.
| |
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!
| |
Finalmente, podemos especificar el comportamiento con los datos que se nos envia en la variable del evento ‘$event’
| |
# @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.
| |
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:
| |
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.
| |
# @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.
| |
# 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.
| |
Posteriormente, desde el componente padre accedemos a la referencia de ng-template usando @ViewChild y renderizamos un componente a nuestro gusto
| |
# 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:
- Proveedores basados en Clases
- Proveedores basados en valores
- Factorias
# 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
| |
Finalmente, en los componentes donde quieres usar el servicio procedes a inyectarlo en el constructor y utilizarlo normalmente:
| |
# 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:
| |
# Resolution Modifiers
Son modificadores para la resolucion de dependencias. Estos pueden ser especificados en los componentes que utilizan Injectables
- self. Es una anotacion que indica que el servicio inyectado en un componente debe definirse como disponible ahi mismo. Es decir, que para que funcione correctamente, se debe proporcionar providers.
| |
- skipself. Es un decorador que indica que un servicio no va a encotnrar disponible en el componente actual, por tanto, puedes saltarlo de la resolucion de dependencias.
| |
optional. Es un decorador que indica que un servicio puede encontrarse o no disponible. Sirve mucho para establecer servicios que solo se encontraran disponibles en un ambiente de produccion y pueden no estar disponibles en produccion. Esto puede ser util con un servicio de Log por ejemplo.
host. Es un decorador que indica que un servicio debera estar disponible una vez por cada componente hijo que tenga el componente actual. Es util para usarlo cuando queremos que dentro de un contenedor cada componente tenga un servicio del mismo tipo
| |
# 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:
| |
Ahora, convertamoslo a un servicio inyectable
| |
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:
| |
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.
| |
Otro caso de uso es utilizar las APIs de Cookie Storage, Local Store, Geolocation, etc. Estas deben ser implementadas usando proveedores por valor.
| |
Gracias a providedIn, este servicio ya se encuentra registrado. Solo queda inyectarlo
| |
# 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
| |
La estructura de un servicio es bastante sencilla.
- name.service.ts. Codigo del servicio
- name.service.spec.ts. Archivo con codigo para los tests
# name.service.ts
La estructura inicial del archivo TS del servicio es la siguiente:
| |
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
| |
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”
| |
Una vez tenemos esto configurado, no hay mas problema. Ahora podemos inyectar un cliente http en cualquier servicio
| |
# Peticiones
Y finalmente, gracias a la configuracion del proxy, podemos facilmente hacer llamadas a la API:
| |
Este mismo objeto http tiene metodos para hacer los distintos tipos de requests
| |
Ademas, existe una forma de crear peticiones personalizadas por nuestra cuenta:
| |
# 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.
| |
Al enviar datos de esta forma todos los que se encuentren suscritos a este Observable recibiran las actualizaciones.
| |
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.
| |
# 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:
| |
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,
| |
La forma normal de obtener un error e sutilizar un stream al cual se le envian los errores cuando este ocurre
| |
# Map Operators
Son un conjunto de operadores que sirven para modificar los datos del stream
| |
# 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:
| |
Antes de utilizarlo, debemos registrarlo en los providers de app.module.ts:
| |
Ahora en su metodo ‘intercept’ podemos clonar el request y enviarla modificada con lo que nosotros queramos, como por ejemplo, unos headers
| |
# 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.
| |
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.
| |
Finalmente, lo registramos en providers
| |
# 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:
- 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.
- 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.
| |
- 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.
| |
Estas rutas deben definirse de mas especificas a menos especificas.
- 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
| |
RouterLinkActive sirve para definir clases de CSS para cuandose enecuentre activo dicho route.
# Dynamic Routes / Link parameters array
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:
| |
Posteriormente, desde un anchor que envie a este link podemos enviarlo:
| |
En caso de que queramos acceder a los datos enviados debemos utilizar ActivatedRoute Service y acceder a su propiedad “params” y leerla con RxJs
| |
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:
| |
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
| |
# 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.
| |
# 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
| |
# 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:
| |
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
| |
# 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.
| |
Posteriormente, debemos utilizar ngModel dentro del HTML para bindear los datos:
| |
Ahora podemos proceder a verificar que todo funcione dinamicamente:
| |
Finalmente, podemos especificar dentro del formulario un ngSumbit para que ejecute una funcion para enviar datos al backend:
| |
Debido al two-way databinding ahora podemos enviar los datos accediendo al modelo sin problemas
| |
# Validacion
La validacion en este tipo de formularios se hace utilizando HTML5.
# Atributos de Validacion
Algunos de los atributos de alidacion relevantes son:
- required. Marca un campo como que es requerido
- minlength / maxlength. Marca la longitud minima o maxima de un campo
- min / max. Marca el valor minimo o maximo para un campo numerico
- pattern. Marca que el valor debe de coincidir con un regex
# Por campo
Para presentar los errores de uno de los campos podemos bindear el input en particular con un modelo e imprimir por pantalla:
| |
# Todo el formulario
Por otro lado, podemos acceder a todo el formulario y conocer si este es completamente valido o invalido:
| |
- valid. Maran si el formulario es valido, es decir, cumple todas las validaciones.
- invalid. Si se incumple cualquiera de las validaciones se trata de un formulario invalido.
- pristine. Marca si el formulario no ha sido modificado, es decir, sigue en el estado en el que fue enviado inicialmente. Se encuentra puro.
- dirty. Marca que el formulario ya sufrio una modificacion
# 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:
| |
Posteriormente, utilizamos los metodos disponibles en NgForm:
| |
# 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
| |
La estructura de una directiva es bastante similar a un servicio pero con Lifecycle Hooks
| |
Para acceder al elemento sobre el cual se esta aplicando la directiva podemos inyectarlo en el constructor:
| |
Y para utilizarla, ocupamos el nombre de la directiva definida en su “selector” propiedad:
| |
A partir de aqui podemos proceder a modificarla!
| |
# 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
| |
# 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
| |
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.
| |
# 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:
| |
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
| |
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.
| |
# 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.
| |
# 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:
- Su codigo no debe ser necesitado por ninguna otra funcionalidad fuera de la aplicacion
- Su codigo no debe depender de nadie mas alla de el mismo para funcionar.
Es decir que un Lazy Module debe de ser un elemento completamente autosuficiente en su funcionamiento.
- Ningun otro modulo deberia conocer la existencia de tu Lazy Module
- 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.
| |
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:
| |
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:
- root. Se puede utilizar este servicio como un Singleton, sin embargo, se pueden crear varios de el si los componentes lo especifican en providers.
- any. Se puede utilizar un servicio por cada Lazy Module, incluido el app inicial. Otra ventaja esque aqui verdaderamente se crea un nuevo servicio por cada Lazy Module, incluido sus injectiontokens
| |
# Route Guards
Son guardias para asegurar las Routes. Las disponibles son:
- canActivate. Es un guard que checa si el end-user es una entidad valida que puede acceder a dicha ruta “Can i activate this route ?”. Es importante conocer que aun asi, se cargara el codigo en el caso de un modulo Lazy
- canActivateChild. Es un guarda que checa si el end-user es una entidad valida para acceder a las rutas hijas (Dentro del array children) de una ruta. “Can i activate the childrens of this route ?”.
- canDeactivate. Es una guarda que checa si el end-user esta autorizado para salir de dicha pagina. Sirve para crear warnings, y otras cosas.
- canLoad. Es una guarda que checa si el end-user es una entidad valida para siquiera cargar el codigo necesario para la Route. Por tanto es aplicable solo para lazy routes.
- Resolve. Esta no esta tan relacionada as las Routes. Esta mas relacionada con el data prefetching. Es comunmente utilizada en las llamadas HTTP, sirve para evitar pantallas de carga y cosas intermedias antes de cargar la pagina, esto debido a que primero va a por los datos y despues carga la pagina.
# Setup
Para crear un Guard podemos utilizar la CLI, tambien nos dara un scaffolding inicial para poder crearla:
| |
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:
| |
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:
| |
# 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)
| |
Otra clase bastante importante es el FormBuilder, mediante este podemos crear Forms complejos. Podemos pasar un objeto con controls basicos:
| |
Finalmente, para renderizarlo en la template, debemos utilizar directive binding llamada formGroup y dentro de ella, los distintos formControl:
| |
# 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
| |
Y se agrega un metodo para lidiar con el submit:
| |
# Disabled
Ademas, podemos definir un campo de un formulario como disabled, esto quiere decir que no se encontrara disponible para la edicion del usuario
| |
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.
| |
# Form Nested
Ademas, se te permite crear un FormGroup dentro de otro FormGroup.
Para hacer esto, basta con agregarlo mediante el formBuilder:
| |
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:
| |
# 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:
| |
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:
| |
# addControl y removeControl
Son dos metodos utiles en el FormBuilder que nos sirve para agregar y remover formcontrols de forma dinamica.
| |
Y finalmente podemos usar las directivas como *ngIf para renderizarlo en el template
| |
# 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.
| |
En este caso se utiliza “Validators” el cual es una coleccion de funciones que pueden ser aplucadas a dicho FormControl.
- max
- min
- required. Marcamos que se necesita que sea True o False
- requiredTrue. En el caso de una checkbox, marcamos que el estado requerido de dicha checkbox debe ser true.
- minLength
- maxLength
- pattern
# 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
| |
# 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.
| |
Posteriormente lo puedes agregar al array de validaciones en el constructor del form control
| |
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.
| |
# 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.
| |
Este validator se agrega en el mismo objeto del FormGroup en el cual podemos indicar cuando ocurren los eventos de actualizacion (“updateOn”)
| |
# 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.
| |
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.
| |
Adicionalmente, en la creacion de un FormGroup este tambien puede ser enviado
- blur. Es un evento que se trigerea cuando te mueves fuera del control (mouseleft).
- change. Se trigerea cuando ocurre cualquier cambio (keyperss)
- submit. En caso de no tener ningun error solo ocurre una vez
# 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
- mergeMap. Hace que los datos se envien en cuanto los datos son cargados, la secuencia y el orden no importan, asi mismo, se hacen envios de datos asincronos.
- switchMap. Cancelara cualquier envio de datos si se reciben nuevos datos, es decir, que los que se suscriban al stream solo deben obtener los datos mas recientes.
- exhaustMap. Hace que los datos se envien solo cuando los datos anteriores se envien, ignorando cualquier actualizacion que ocurra hasta que los que se suscriban reciban los datos de forma ocrrecta.
# Custom Pipe
Adicionalmente, tambien podemos construir un custom pipe. Para inicializarlo podemos utilizar el CLI:
| |
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.
| |
Posteriormente se registra en el modulo app dentro de los providers, es un provider mas pero este es registrado como error handler
| |
Finalmente, podemos lidiar con errores utilizando esta clase.