Framework: Angular
==================
.. image:: /logos/logo-angular.png
:scale: 20%
:alt: Logo Angular
:align: center
.. |date| date::
.. |time| date:: %H:%M
Documentación básica de Angular 12
.. contents:: Índice
Configuraciones
###############
Instalación de Angular CLI
**************************
Para gestionar Angular hay que instalar Angular CLI: ``npm install -g @angular/cli``
.. note::
Para instalar en Linux utilizar el comando ``sudo npm install -g @angular/cli``
ANGULAR CLI y otros comandos
############################
Para ejecutar un comando de Angular CLI el prefijo que se utiliza es ``ng``:
* Comprobar versión de angular/cli: ``ng --version``
* Crear proyecto: ``ng new nombre-proyecto``
* Levantar servidor y abrir navegador: ``ng serve -o`` (ruta por defecto: localhost:4200)
* Levantar en otro puerto: ``ng serve -o --port 4500``
* Crear un componente: ``ng generate component nombre_componente``
* Crear componente en linea: ``ng generate component comonente_linea -s -t``
* Crear un pipe: ``ng generate pipe nombrePipe``
* Crear un servicio: ``ng generate service nombreServicio``
* Crear interface en angular: ``ng generate interface nombreInterface``
* Construir aplicación: ``ng build`` (se guarda en la carpeta dist)
* Instalar librerías en angular: ``ng add @angular/material``
Otros comandos útiles de npm
****************************
* Instalar todas las dependencias: ``npm install`` (no es un comando ng pero es importante recordarlo)
* Instalar servidor para aplicación construida: ``npm install -g serve``
* Ejecutar aplicación angular ya construida (desde su carpeta dentro de dist): ``serve``
.. attention::
Para ejecutar en Windows estos comandos CLI se usará la aplicación ``nodejs command prompt`` o ejecutar con el prefijo ``npm run ng``
Estructura del proyecto
#######################
Partes importantes del proyecto:
* package.json: archivo con las dependencias del proyecto.
* angular.json: se encuentra la configuración del proyecto.
* e2e: Carpeta test de integración
* src: carpeta del proyecto en la cual vemos:
- index.html: pagina de entrada de la aplicación.
- main.ts: es donde se cargan los módulos.
- style.css: estilos a nivel global.
- test.ts: archivo para realizar tests.
- enviorments: carpeta para variables de entornos.
- assets: carpeta de archivos estáticos como imágenes.
- app: carpeta del módulo principal donde se irá añadiendo el resto de componentes.
Componentes
###########
Los componentes se encuentran en la carpeta **src**.
Es ideal crear una carpeta **components** dentro de **src** para ordenarlos ya que se van a reutilizar en distintos módulos.
Cada componente se organizará en una carpeta con el siguiente contenido:
* Hoja de estilo CSS u otro tipo.
* Archivo HTML.
* Controlador Typescript.
* Modulo Typescript.
Crear un componente
*******************
Para crear un componente se ejecuta el comando: ``ng generate component nombre_componente``
Crear componente dentro de una carpeta **components**: ``ng generate component components/menu``
* Lo primero que vamos a hacer es borrar el contenido del archivo **app.component.html** y lo reemplazamos por:
.. code-block::
:linenos:
Componente principal
* Ahora editamos el contenido de **menu.component.html**:
.. code-block::
:linenos:
Soy el menú
* Y de paso editamos el css de menu en **menu.component.css**:
.. code-block:: css
:linenos:
h2{
color: red;
}
Esto mostrará el título del módulo y el subtítulo del componente menú de color rojo.
.. attention::
Mover la carpeta de un componente de forma manual causará fallos en la aplicación ya que no coincidirán las
rutas.
Crear componente en línea
*************************
Un componente en línea contiene en un solo archivo ts, la lógica, el código html y el código css:
* Crear componente en linea: ``ng generate component comonente_linea -s -t``
Data Binding
############
Atributos
*********
* Crear y asignar en componente **app.component.ts**:
.. code-block:: Typescript
:linenos:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// crear una variable en el componente:
mensaje: string = "Mensaje desde el componente app";
}
* Utilizar variable en plantilla **app.template.html**:
.. code-block::
:linenos:
{{mensaje}}
Métodos
*******
Retornar datos de un método:
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-interpolation',
templateUrl: './interpolation.component.html',
styleUrls: ['./interpolation.component.css']
})
export class InterpolationComponent implements OnInit {
// vamos a crear un objeto mixto sin interface:
consola: any = {
marca: 'Sony',
modelo: 'PlayStation',
lanzamiento: new Date(1995, 9, 29)
}
constructor() { }
ngOnInit(): void {
}
// para averiguar la edad creamos un método:
getEdad(): number {
const edad = (new Date().getTime() - this.consola.lanzamiento.getTime()) / (365 * 24 * 60 * 60 * 1000);
return Math.ceil(edad);
}
}
Uso en el template:
.. code-block::
:linenos:
Consola: {{consola.marca}} {{consola.modelo}}
Edad: {{getEdad()}} años
Assets
******
* Tratamiento estático de assets en templates:
.. code-block::
:linenos:
* Asignación de assets en componentes:
.. code-block:: Typescript
:linenos:
imagen: string = '/assets/img/prueba-imagen.jpg';
Atributos dinámicos
*******************
* En el controlador existe un atributo con una ruta, se carga la variable en el template:
.. code-block::
:linenos:
.. note::
Esto vale para cargar información en cualquier atributo de la etiqueta.
Eventos del DOM (event binding)
*******************************
Angular dispone de todos los métodos tácticos del DOM usados en HTML para dispara acciones:
* En el componente se crea un método:
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-saludar',
templateUrl: './saludar.component.html',
styleUrls: ['./saludar.component.css']
})
export class SaludarComponent implements OnInit {
// se crea un atributo para el HTML:
public saludo: string;
constructor() {
// Se inicializa el atributo con un mensaje o nada:
this.saludo = "";
}
ngOnInit(): void {
}
// este método dispara el saludo:
startSaludo(): void{
this.saludo = "Hola amigo, ¿quién eres?";
}
}
* Este método ahora se puede disparar al hacer click en el botón:
.. code-block::
:linenos:
Ejemplo de saludo
{{ saludo }}
Recuperar valores de plantilla (Two Way binding)
************************************************
Para recuperar valores de la plantilla como datos de un formulario se hace lo siguiente:
1. Se carga el modulo de formularios en **app.module.ts**:
.. code-block:: Typescript
:linenos:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MenuComponent } from './components/menu/menu.component';
import { InterpolationComponent } from './components/interpolation/interpolation.component';
import { PropertyBindingComponent } from './components/property-binding/property-binding.component';
import { EventBindingComponent } from './components/event-binding/event-binding.component';
import { SaludarComponent } from './components/saludar/saludar.component';
import { TwowaybindingComponent } from './components/twowaybinding/twowaybinding.component';
import { FormsModule } from '@angular/forms'; // se importan los forms.
@NgModule({
declarations: [
AppComponent,
MenuComponent,
InterpolationComponent,
PropertyBindingComponent,
EventBindingComponent,
SaludarComponent,
TwowaybindingComponent
],
imports: [
BrowserModule,
FormsModule // se carga en los imports el modulo de forms para todos los componentes de app
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Ahora pasamos al componente con el que se quiere trabajar:
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-twowaybinding',
templateUrl: './twowaybinding.component.html',
styleUrls: ['./twowaybinding.component.css']
})
export class TwowaybindingComponent implements OnInit {
// crear objeto donde se va a guardar los datos.
consola: any = {
marca: null,
modelo: null
}
constructor() { }
ngOnInit(): void {
}
}
3. Y por último se prepara el template:
.. code-block::
:linenos:
Datos consola
Consola: {{consola.marca}} {{consola.modelo}}
Ciclo de vida
#############
El ciclo de vida va en el siguiente orden:
* Constructor
* ngOnChanges
* ngOnInit
* ngDoCheck
* ngAfterContentInit
* ngAfterContentChecked
* ngAfterViewInit
* ngAfterViewChecked
* ngOnDestroy
Constructor - Cargar métodos y datos que construyen la aplicación
*****************************************************************
La primera acción que se ejecuta en el componente es el constructor y es muy útil para
cargar la información antes de disparar cualquier evento como NgOnInit():
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-event-binding',
templateUrl: './event-binding.component.html',
styleUrls: ['./event-binding.component.css']
})
export class EventBindingComponent implements OnInit {
// se crea un elemento, si no se inicializa dará error el linter:
public hora: string;
constructor() {
// establecemos la hora con el setHora:
this.hora = this.setHora();
}
ngOnInit(): void {
}
// creamos un seteador para la hora:
setHora(): string {
// recuperamos hora, minutos y segundos actuales:
const hh = ('0' + new Date().getHours()).slice(-2);
const mm = ('0' + new Date().getHours()).slice(-2);
const ss = ('0' + new Date().getHours()).slice(-2);
// cargamos el tiempo en hora:
return hh + ':' + mm + ':' + ss;
}
}
ngOnInit() - Cargar método cuando se inicialice el componente
*************************************************************
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-property-binding',
templateUrl: './property-binding.component.html',
styleUrls: ['./property-binding.component.css']
})
export class PropertyBindingComponent implements OnInit {
imagen: string = '/assets/img/prueba-imagen.jpg';
constructor() { }
ngOnInit(): void {
// lo cargamos en el DOM al inicializar el componente para que se ejecute:
this.cambiarImagen();
}
// crear metodo que cambia imagen:
cambiarImagen(): void {
const logoRojo = '/assets/img/logo-rojo.jpg';
const logoBlanco = '/assets/img/logo-blanco.jpg';
setInterval(()=> {
this.imagen === logoRojo ? this.imagen = logoBlanco : this.imagen = logoRojo;
}, 1000);
}
}
ngAfterContentChecked() - Ejecutar después de que se refresque un componente
****************************************************************************
Este evento se dispará cuando angular refresca un componente permitiendo ejecutar métodos adicionales en el proceso.
* caso de ejemplo con un marcador cuyos jugadores se deben ordenar por los que más han encanastado:
.. code-block:: typescript
:linenos:
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-top-score',
templateUrl: './top-score.component.html',
styleUrls: ['./top-score.component.css']
})
export class TopScoreComponent implements OnInit {
@Input() equipoLocal: any;
@Input() equipoVisitante: any;
jugadores: any = [];
constructor() { }
ngOnInit(): void {
this.jugadores = [...this.equipoLocal.jugadores, ...this.equipoVisitante.jugadores];
}
// hook para dispararse cada vez que haya cambios en los valores:
ngAfterContentChecked(){
this.sortJugadores();
}
sortJugadores() {
this.jugadores.sort( (a: any, b: any)=> {
return (b.puntos - a.puntos);
} );
}
}
Pipes
#####
El pipe permite formatear valores que vienen del componente.
Pipes de texto
**************
* Tenemos un atributo texto en el componente:
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-pipes-angular',
templateUrl: './pipes-angular.component.html',
styleUrls: ['./pipes-angular.component.css']
})
export class PipesAngularComponent implements OnInit {
// se crean un atributo con datos:
texto: string = 'La mejor consola es la Switch'
constructor() { }
ngOnInit(): void {
}
}
* En el template vamos a ver los pipes:
.. code-block::
:linenos:
Pipes de texto
Nombre
Valor sin pipe
Valor con pipe
Descripción
Uppercase
{{ texto }}
{{ texto|uppercase }}
Todas las letras a mayúsculas
Lowercase
{{ texto }}
{{ texto|lowercase }}
Todas las letras a minúsculas
Titlecase
{{ texto }}
{{ texto|titlecase }}
todas las primeras letras a mayúsculas
Pipes de formato y fecha
************************
* Tenemos los siguientes atributos:
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-pipes-angular',
templateUrl: './pipes-angular.component.html',
styleUrls: ['./pipes-angular.component.css']
})
export class PipesAngularComponent implements OnInit {
// se crea un atributo de tipo mixto:
id: any = 11;
// también creamos una fecha:
fecha: Date = new Date();
constructor() {
// ahora se combina con una nomenclatura:
this.id = '000' + this.id;
}
ngOnInit(): void {
}
}
* Y estos son los pypes:
.. code-block::
:linenos:
Pipes de formato y fecha
Nombre
Valor sin pipe
Valor con pipe
Descripción
Slice
{{ id }}
{{ id|slice: -3 }}
Corta un número de caracteres
Date
{{ fecha }}
{{ fecha|date: 'dd/MM/yyyy hh:mm' }}
Formatea una fecha, también disponible opciones: "long", "medium", "short"
Pipes núméricos
***************
* Estos son los atributos a modificar:
.. code-block:: Typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-pipes-angular',
templateUrl: './pipes-angular.component.html',
styleUrls: ['./pipes-angular.component.css']
})
export class PipesAngularComponent implements OnInit {
// se crea un atributo numérico:
importe: number = 1927.327823;
constructor() {
}
ngOnInit(): void {
}
}
* Veamos los pipes numéricos:
.. code-block::
:linenos:
Pipes Numéricos
Nombre
Valor sin pipe
Valor con pipe
Descripción
Decimal
{{ importe }}
{{ importe|number: "5.2-2" }}
Establece la cantidad de números mínimos de enteros y los decimales
Currency
{{ importe }}
{{ importe|currency: "€" }}
Trabaja con divisas, por defecto $ pero se puede cambiar por otro
Crear un pipe
*************
* Para crear un Pipe se utiliza el comando: ``ng generate pipe nombrePipe``
* Generar un pipe en una carpeta: ``ng generate pipe pipes/nombrePipe``
* Editar el pipe:
.. code-block:: typescript
:linenos:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'nombrePipe'
})
export class NombrePipePipe implements PipeTransform {
// el metodo transform recibe siempre el value y luego podemos definir el tipo de dato que retorna:
transform(value: number, decimales: number, moneda?: string): number | string { // se puede poner el parametro opcional ?
// vamos a hacer un redondeo:
const factor = Math.pow(10,decimales);
let valorRedondeado;
if(value >= 0){
valorRedondeado = Math.round(value * factor) / factor;
}else{
valorRedondeado = Math.round(-value * factor) / factor;
}
// formatear el valor numérico similar al nuestro:
let valorFormateado = new Intl.NumberFormat('de-DE', {minimumFractionDigits: decimales}).format(valorRedondeado);
return moneda ? valorFormateado + ' ' + moneda : valorFormateado;
}
}
Ahora esto se utiliza en un número decimal del template ``{{ number|nombrePipe:2 }}`` o con una moneda ``{{ number|nombrePipe:2:'€' }}`` y lo redondeará a un entero.
.. note::
Los args son opcionales, se puede utilizar el método transform solo con el parametro value.
Directivas angular
##################
Las directivas sirven para controlar el comportamiento de la información desde el template.
.. attention::
cuando se utilizan números en las cadenas estos se escriben ``*ngSwitchCase="20"``, si son cadenas ``*ngSwitchCase="'hola'"`` y si son atributos ``*ngSwitchCase="atributo"``
Directivas de control
*********************
Condicional if
++++++++++++++
* Creamos un atributo edad en el componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-directiva-if',
templateUrl: './directiva-if.component.html',
styleUrls: ['./directiva-if.component.css']
})
export class DirectivaIfComponent implements OnInit {
// ponemos una edad:
public edad: number;
constructor() {
// inicializamos la edad:
this.edad = 0;
}
ngOnInit(): void {
}
}
* En el template veremos el ngIf:
.. code-block::
:linenos:
ngIf
Tienes {{ edad }} años.
=18; else menor">Eres mayor de edad
Eres menor de edad
.. attention::
Hay que añadir el módulo FormsModule en app.module.ts
Condicional Switch
++++++++++++++++++
* Tenemos una edad en el componente igual al ejemplo anterior.
* En el template veremos ngSwitch:
.. code-block::
:linenos:
ngIf
Tienes {{ edad }} años.
Ya eres mayor de edad!
Ya eres un anciano!
Condicional For
+++++++++++++++
* Creamos una lista de elementos en el componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-directiva-for',
templateUrl: './directiva-for.component.html',
styleUrls: ['./directiva-for.component.css']
})
export class DirectivaForComponent implements OnInit {
// crear colección:
consolas: Array;
constructor() {
// harcodear datos:
this.consolas = [
{marca: 'Sony', modelo: 'Playstation', lanzamiento: 1995},
{marca: 'Sega', modelo: 'Dreamcast', lanzamiento: 1998},
{marca: 'Nintendo', modelo: 'Gameboy', lanzamiento: 1989},
{marca: 'Nintendo', modelo: 'Gamecube', lanzamiento: 2002},
{marca: 'Sony', modelo: 'Playstation 2', lanzamiento: 2001}
]
}
ngOnInit(): void {
}
}
* Cargar listado en el template:
.. code-block: html
:linenos:
Listado de consolas con *ngFor
ID
Consola
Marca
Lanzamiento
{{ i + 1 }}
{{ consola.modelo }}
{{ consola.marca }}
{{ consola.lanzamiento }}
.. note::
Una cosa interesante es que cuando se producen cambios en la información del componente esto se actualizan en la vista automáticamente sin hacer nada.
Directivas de atributos
***********************
Las directivas de atributos al igual que los atributos dinámicos [src], [value], etc... permiten asignar un valor que proviene del componente como el
nombre de una clase o el valor de un estilo css.
Implementar clase ngClass
+++++++++++++++++++++++++
* Tenemos el listado de consolas en el componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-clases',
templateUrl: './clases.component.html',
styleUrls: ['./clases.component.css']
})
export class ClasesComponent implements OnInit {
consolas: Array;
constructor() {
// harcodear datos:
this.consolas = [
{marca: 'Sony', modelo: 'Playstation', lanzamiento: 1995},
{marca: 'Sega', modelo: 'Dreamcast', lanzamiento: 1998},
{marca: 'Nintendo', modelo: 'Gameboy', lanzamiento: 1989},
{marca: 'Nintendo', modelo: 'Gamecube', lanzamiento: 2002},
{marca: 'Sony', modelo: 'Playstation 2', lanzamiento: 2001}
]
}
ngOnInit(): void {
}
}
* En el css se crean unas clases con los nombres de las marcas:
.. code-block:: css
:linenos:
.Sony{
color: gray;
}
.Sega{
color: blue;
}
.Nintendo{
color: red;
}
* Ahora en el template se implementan las clases según su marca:
.. code-block::
:linenos:
ID
Consola
Marca
Lanzamiento
{{ i + 1 }}
{{ consola.modelo }}
{{ consola.marca }}
{{ consola.lanzamiento }}
Implementar estilo ngStyle
++++++++++++++++++++++++++
* En primer lugar añadimos un atributo edad y un método que defina los colores:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-clases',
templateUrl: './clases.component.html',
styleUrls: ['./clases.component.css']
})
export class ClasesComponent implements OnInit {
edad: number;
constructor() {
this.edad = 0;
}
ngOnInit(): void {
}
// vamos a crear un método para recuperar el color:
getColor(){
if(this.edad < 18){
return "red";
}else if(this.edad >= 18){
return "green";
}else{
return "blue";
}
}
}
* En el template se implementa la directiva:
.. code-block::
:linenos:
Tienes {{ edad }} años.
=18; else menor" [ngStyle]="{'color': getColor()}">Eres mayor de edad
Eres menor de edad
Jerarquía de componentes
########################
Enviar valores de padre a hijo
******************************
* El componente padre tiene un valor:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-padre',
templateUrl: './padre.component.html',
styleUrls: ['./padre.component.css']
})
export class PadreComponent implements OnInit {
// tenemos un valor en el padre:
numeroPadre : number;
constructor() {
// al que se le asigna un número:
this.numeroPadre = 30;
}
ngOnInit(): void {
}
}
* El padre envía a través del selector del hijo el atributo numeroPadre:
.. code-blocK::
:linenos:
Componente padre
Valor número padre: {{ numeroPadre }}
* El hijo se prepara en el componente para recibir valores del padre:
.. code-block:: typescript
:linenos:
// se importa input:
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-hijo',
templateUrl: './hijo.component.html',
styleUrls: ['./hijo.component.css']
})
export class HijoComponent implements OnInit {
// con el decorador Input se prepara el atributo para recibir los datos:
@Input() numeroHijo : number = 0;
constructor() { }
ngOnInit(): void {
}
}
* Ahora se puede utilizar el atributo numeroHijo con los datos del padre:
.. code-block::
:linenos:
Componente hijo
Valor número hijo: {{ numeroHijo }}
Enviar valores de hijo a padre
******************************
* El procedimiento es similar, esta vez comenzamos por el componente hijo:
.. code-block:: typescript
:linenos:
// se importa output y también el emisor de eventos:
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-hijo',
templateUrl: './hijo.component.html',
styleUrls: ['./hijo.component.css']
})
export class HijoComponent implements OnInit {
// con el decorador Output se crea un nuevo objeto de tipo emisor:
@Output() valor : EventEmitter = new EventEmitter();
constructor() { }
ngOnInit(): void {
}
// preparamos un método para disparar el evento:
handleChangeValor(){
this.valor.emit({numeroHijo: 50})
}
}
* Añadimos un botón en el hijo que ejecutará el método emisor:
.. code-block::
:linenos:
Componente hijo
* El padre deberá tener un método para recibir el evento emitido y actualizar el atributo numérico:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-padre',
templateUrl: './padre.component.html',
styleUrls: ['./padre.component.css']
})
export class PadreComponent implements OnInit {
numeroPadre : number;
constructor() {
this.numeroPadre = 0;
}
ngOnInit(): void {
}
// recibimos el valor y lo asignamos al numeroPadre:
getValor($event: any): void{
this.numeroPadre = $event.numeroHijo;
}
}
* La plantilla solo tendrá que mostrar el número por defecto que cambiará al pulsar el botón:
.. code-block::
:linenos:
Componente padre
Valor recibido: {{ numeroPadre }}
Servicios en Angular
####################
* Crear un servicio: ``ng generate service nombreServicio``
* Crear un servicio en un directorio: ``ng generate service services/nombreServicio``:
.. code-block:: Typescript
:linenos:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root' // esto indica que el servicio estará disponible en toda la aplicación
})
export class ConsolasService {
// se crean los atributos como privados:
private consolas: any = [
{marca: 'Sony', modelo: 'Playstation', lanzamiento: 1995},
{marca: 'Sega', modelo: 'Dreamcast', lanzamiento: 1998},
{marca: 'Nintendo', modelo: 'Gameboy', lanzamiento: 1989},
{marca: 'Nintendo', modelo: 'Gamecube', lanzamiento: 2002},
{marca: 'Sony', modelo: 'Playstation 2', lanzamiento: 2001}
]
constructor() { }
// se generan los métodos para obtener el listado:
getConsolas(): any {
return this.consolas;
}
}
* Implementamos el servicio en un componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
// se importa el servicio:
import { ConsolasService } from 'src/app/services/consolas.service';
@Component({
selector: 'app-directiva-for',
templateUrl: './directiva-for.component.html',
styleUrls: ['./directiva-for.component.css']
})
export class DirectivaForComponent implements OnInit {
// crear colección:
consolas: Array = [];
// se instancia por parámetros el servicio:
constructor(private consolasService: ConsolasService) {
}
ngOnInit(): void {
// cuando haya cargado la página y obtenido los clientes se carga en el Init:
this.consolas = this.consolasService.getConsolas();
}
}
* Ya se puede usar el servicio en el template:
.. code-block::
:linenos:
Listado de consolas con *ngFor
ID
Consola
Marca
Lanzamiento
{{ i + 1 }}
{{ consola.modelo }}
{{ consola.marca }}
{{ consola.lanzamiento }}
Cargar datos en servicio
************************
Para enviar información al servicio hacemos lo siguiente:
* Ahora se modifica el servicio para añadir un set:
.. code-block:: typescript
:linenos:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ConsolasService {
private consolas: any = [
{marca: 'Sony', modelo: 'Playstation', lanzamiento: 1995},
{marca: 'Sega', modelo: 'Dreamcast', lanzamiento: 1998},
{marca: 'Nintendo', modelo: 'Gameboy', lanzamiento: 1989},
{marca: 'Nintendo', modelo: 'Gamecube', lanzamiento: 2002},
{marca: 'Sony', modelo: 'Playstation 2', lanzamiento: 2001}
]
constructor() { }
getConsolas(): any {
return this.consolas;
}
// crear un set que añadirá el nuevo elemento en la lista:
setConsola(consola: any): void {
this.consolas.push(consola);
}
}
* Crear un componente por ejemplo **crearConsola** y editar su componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
// se importa el servicio:
import { ConsolasService } from 'src/app/services/consolas.service';
@Component({
selector: 'app-crear-consola',
templateUrl: './crear-consola.component.html',
styleUrls: ['./crear-consola.component.css']
})
export class CrearConsolaComponent implements OnInit {
// se prepara el objeto para añadir una consola:
consola: any = {
marca: '',
modelo: '',
lanzamiento: ''
}
// se carga el servicio en el constructor:
constructor(private consolasService: ConsolasService) { }
ngOnInit(): void {
}
// preparamos un método para añadir consola:
crearConsola(): void{
this.consolasService.setConsola(this.consola);
}
}
* En su template tendrá un formulario:
.. code-blocK::
:linenos:
.. attention::
Recuerda que hay que añadir FormsModule en el módulo principal app.
Interfaces Angular
##################
* Crear interface en angular: ``ng generate interface nombreInterface``
* Crear en una carpeta específica: ``ng generate interface interfaces/nombreInterface``
.. code-block:: typescript
:linenos:
export interface Consola {
marca: string,
modelo: string,
lanzamiento: number
}
Ahora para establecerlo como un tipo de propiedad lo asignamos al atributo:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
// se importa la interfaz consolas:
import { Consolas } from 'src/app/interfaces/consolas';
import { ConsolasService } from 'src/app/services/consolas.service';
@Component({
selector: 'app-crear-consola',
templateUrl: './crear-consola.component.html',
styleUrls: ['./crear-consola.component.css']
})
export class CrearConsolaComponent implements OnInit {
// se prepara el objeto para añadir una consola:
consola: Consolas = {
marca: '',
modelo: '',
lanzamiento: 0
}
constructor(private consolasService: ConsolasService) { }
ngOnInit(): void {
}
crearConsola(): void{
this.consolasService.setConsola(this.consola);
}
}
Esta interfaz se puede aplicar en donde se necesite ya sea controladores o servicios.
.. note::
Si tenemos que definir un listado de objetos podemos declararlo usando el nombre de la intefaz: ``consolas: Array = [];``
Rutas en angular
################
Si hemos añadido **angular routing** para crear nuestro proyecto podemos usar las rutas de angular de forma sencilla.
* Se crean dos componentes nuevo para páginas: ``ng generate component pages/index`` y ``ng generate component pages/about``
* Al iniciar le proyecto se crea un archivo en **apps** llamado **app-routing.module.ts** que vamos a editar:
.. code-block:: typescript
:linenos:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
// importamos los componentes:
import { AboutComponent } from './pages/about/about.component';
import { IndexComponent } from './pages/index/index.component';
// Aquí se van añadiendo las rutas:
const routes: Routes = [
{path: '', component: IndexComponent}, // añadimos la ruta raiz
{path: 'about', component: AboutComponent} // y las rutas que tengamos según componentes
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Ahora se añade el enrutador al template de **app.component.html**:
.. code-block::
:linenos:
Enlaces de rutas
****************
Para crear enlaces en las rutas hacemos lo siguiente en un template como **app.component.html**:
.. code-block::
:linenos:
Rutas
.. note::
Lo ideal es crear un nuevo componente para un navbar
Rutas no existente
******************
Para solucionar las rutas inexistentes se crea un componente 404: ``ng generate component pages/error``
* Se añade al enrutador:
.. code-block:: typescript
:linenos:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutComponent } from './pages/about/about.component';
import { ErrorComponent } from './pages/error/error.component';
// se importa el componente:
import { IndexComponent } from './pages/index/index.component';
const routes: Routes = [
{path: '', component: IndexComponent},
{path: 'about', component: AboutComponent},
{path: '**', component:ErrorComponent} // va añadido siempre al final esta ruta
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Ahora quedaría retocar la plantilla de la página para que muestre un error 404 personalizado.
Formularios Reactivos
#####################
Vamos a trabajar con **ReactiveFormsModule** para implementar validaciones más potentes y otras características.
* Lo primero será cargar la librería **ReactiveFormsModule** en **app.module.ts**:
.. code-block:: typescript
:linenos:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AboutComponent } from './pages/about/about.component';
import { IndexComponent } from './pages/index/index.component';
import { ErrorComponent } from './pages/error/error.component';
import { CrearConsolaComponent } from './componets/crear-consola/crear-consola.component';
// se importa la librería ReactiveFormsModule
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
AppComponent,
AboutComponent,
IndexComponent,
ErrorComponent,
CrearConsolaComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule // y se declara para usar en el modulo app
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
* Ahora se trabaja el componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
// se importa formgroup, formcontrol y validators:
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-crear-consola',
templateUrl: './crear-consola.component.html',
styleUrls: ['./crear-consola.component.css']
})
export class CrearConsolaComponent implements OnInit {
// creamos el atributo que manejara el form con el tipo FormGroup:
public consolaForm: FormGroup;
constructor() {
// se implementa el formulario con sus campos:
this.consolaForm = new FormGroup({ // también se aplicarán los validadores:
marca: new FormControl('', [Validators.required]),
modelo: new FormControl('', [Validators.required]),
lanzamiento: new FormControl(0, [Validators.required])
});
}
ngOnInit(): void {
}
enviarConsola(): void{
console.log('se dispara');
console.log(this.consolaForm.value);
}
}
* Ahora pasamos al template a crear el formulario:
.. code-block::
:linenos:
{{ consolaForm.value | json }}
Tipos de validaciones comunes:
- pristine: limpio, nada escrito.
- dirty: se ha escrito.
- touched: ha sido seleccionado.
- untouched: no esta seleccionado.
- valid: es válido.
- invalid: no es válido.
Peticiones HTTP
###############
Partimos de un proyecto nuevo creado con ``ng new consolasfront``
Preparativos
************
Lo primero será cargar el modulo **HttpClientsModule** en los imports de **app.module.ts**:
.. code-block:: typescript
:linenos:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
import { DirectivaIfComponent } from './components/directiva-if/directiva-if.component';
import { DirectivaForComponent } from './components/directiva-for/directiva-for.component';
import { CrearConsolaComponent } from './components/crear-consola/crear-consola.component'; // se importan los forms.
// se carga la librería HttpClientModule:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent,
DirectivaForComponent,
CrearConsolaComponent,
],
imports: [
BrowserModule,
HttpClientModule, // cargamos el httpclientmodule
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Crear servicio Http
*******************
Se crea un nuevo servicio en angular con ``ng generate service services/consolas`` y se edita:
.. code-block:: typescript
:linenos:
// se importa la librería http:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
// importamos map de rxjs:
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ConsolasService {
// definimos la ruta base que será la del servidor API:
base: string = 'http://localhost:3500';
// se carga la librería http en el constructor:
constructor(private http: HttpClient) { }
// utilizamos el getter para hacer la petición get:
getConsolas(): any {
// esto retorna una petición con get y una respuesta con pipe:
return this.http.get(this.base)
.pipe( // se mapea la respuesta con rxjs:
map((data: any)=> {
return data;
})
);
}
// recuperar un solo valor del mismo modo que antes pero con un endpoint recibido por parametros:
getConsola(marca: any): any {
return this.http.get(`${this.base}/consola/${marca}`)
.pipe(
map((data: any)=> {
return data;
})
);
}
// Hacemos un método POST:
postConsola(consola: any): any {
return this.http.post(`${this.base}/consola/crear/`, consola)
.pipe(
map((data: any) => {
return data;
})
);
}
// Hacemos un método PUT:
putConsola(consola: any, modelo: string): any {
return this.http.put(`${this.base}/consola/actualizar/${modelo}`, consola)
.pipe(
map((data: any) => {
return data;
})
);
}
// hacemos el metodo delete:
deleteConsola(modelo: string){
return this.http.delete(`${this.base}/consola/eliminar/${modelo}`)
.pipe(
map((data: any) => {
return data;
})
);
}
}
* Creamos un nuevo componente ``ng generate components pages/index`` para sacar un listado de consolas.
* Se crea un nuevo componente ``ng generate component pages/crearConsola``
* Se crea un componente ``ng generate component pages/actualizar``
* Añadimos las rutas de los nuevos componentes:
.. code-block:: typescript
:linenos:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ActualizarComponent } from './pages/actualizar/actualizar.component';
import { CrearConsolaComponent } from './pages/crear-consola/crear-consola.component';
import { IndexComponent } from './pages/index/index.component';
const routes: Routes = [
{path: '', component: IndexComponent},
{path: 'crear', component: CrearConsolaComponent},
{path: 'actualizar/:modelo', component: ActualizarComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Peticiones GET
**************
* Se edita el componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
import { Consolas } from 'src/app/interfaces/consolas';
import { ConsolasService } from 'src/app/services/consolas.service';
@Component({
selector: 'app-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.css']
})
export class IndexComponent implements OnInit {
// se crea una colección de objetos de tipo consolas:
consolas: Array = [];
// se carga el servicio en el constructor:
constructor(private consolasService: ConsolasService) {
}
ngOnInit(): void {
// hay que subscribir los datos a la hora de obtenerlos con rxjs:
this.consolasService.getConsolas() // o se usa any o se valida que es de tipo colección de consolas:
.subscribe((res:Array)=>{
this.consolas = res;
}, (err: any) =>{ // si falla se ejecuta esta linea
console.log(err);
});
}
}
* Se edita el template:
.. code-block::
:linenos:
Listado de consolas
Marca
Modelo
Lanzamiento
{{ consola.marca }}
{{ consola.modelo }}
{{ consola.lanzamiento }}
Peticiones POST
***************
* se edita el componente:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
// cargamos el router para una redirección:
import { Router } from '@angular/router';
// se importa la interfaz consolas:
import { Consolas } from 'src/app/interfaces/consolas';
import { ConsolasService } from 'src/app/services/consolas.service';
@Component({
selector: 'app-crear-consola',
templateUrl: './crear-consola.component.html',
styleUrls: ['./crear-consola.component.css']
})
export class CrearConsolaComponent implements OnInit {
// se prepara el objeto para añadir una consola:
consola: Consolas = {
marca: '',
modelo: '',
lanzamiento: 0
}
// se trae el servicio de consolas y el router para uan redirección:
constructor(private consolasService: ConsolasService, private route: Router) { }
ngOnInit(): void {
}
crearConsola(): void{
this.consolasService.postConsola(this.consola)
.subscribe((res: any) => {
console.log(res); // esto se quita en producción
// haremos una redirección al listado:
this.route.navigate(['/']);
}, (err: any) => {
console.log(err);
});
}
}
* Y ahora se edita el template:
.. code-block::
:linenos:
Crear nueva consola
.. note::
Se debería crear un comnponente solo para el formulario y reutilizar en la vista editar pero esto es un ejemplo básico.
Peticiones PUT
**************
* En el template del listado se añade el botón con un parámetro para buscar el elemento:
.. code-block::
:linenos:
Listado de consolas
Marca
Modelo
Lanzamiento
{{ consola.marca }}
{{ consola.modelo }}
{{ consola.lanzamiento }}
* Se edita el componente actualizar:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
// cargar el interface:
import { Consolas } from 'src/app/interfaces/consolas';
// importar el servicio:
import { ConsolasService } from 'src/app/services/consolas.service';
@Component({
selector: 'app-actualizar',
templateUrl: './actualizar.component.html',
styleUrls: ['./actualizar.component.css']
})
export class ActualizarComponent implements OnInit {
// crear un atributo donde guardar la marca:
modelo: string;
// se prepara el objeto para añadir una consola:
consola: Consolas = {
marca: '',
modelo: '',
lanzamiento: 0
}
// cargar el modulo de ruta activa para coger el parámetro get y el servicio:
constructor(private route: ActivatedRoute, private router: Router, private consolasService: ConsolasService) {
// asignar el valor de modelo recibido por parámetros:
this.modelo = this.route.snapshot.params['modelo'];
// nos subscribimos al servicio:
this.consolasService.getConsola(this.modelo)
.subscribe((res: any) =>{
console.log(res);
}, (err: any) => {
console.log(err);
});
}
ngOnInit(): void {
}
editarConsola(): void {
this.consolasService.putConsola(this.consola, this.modelo)
.subscribe((res: any) =>{
console.log(res);
// redireccionamos:
this.router.navigate(['/']);
}, (err: any) => {
console.log(err);
});
}
}
* Crear el template:
.. code-block::
:linenos:
Editar {{modelo}}
Peticiones DELETE
*****************
* Empezamos en el template del listado:
.. code-block::
:linenos:
Listado de consolas
Marca
Modelo
Lanzamiento
{{ consola.marca }}
{{ consola.modelo }}
{{ consola.lanzamiento }}
* Creamos el metodo y hacemos la petición delete:
.. code-block:: typescript
:linenos:
import { Component, OnInit } from '@angular/core';
import { Consolas } from 'src/app/interfaces/consolas';
import { ConsolasService } from 'src/app/services/consolas.service';
@Component({
selector: 'app-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.css']
})
export class IndexComponent implements OnInit {
consolas: Array = [];
constructor(private consolasService: ConsolasService) {
}
ngOnInit(): void {
this.cargarClientes();
}
eliminarConsola(modelo: string){
this.consolasService.deleteConsola(modelo)
.subscribe((res:any)=>{
// refrescar pantalla:
this.cargarClientes();
}, (err: any)=>{
console.log(err);
});
}
// recuperamos la petición del listado y la añadimos a esta función para reutilizar de manera limpia:
cargarClientes(){
this.consolasService.getConsolas()
.subscribe((res:Array)=>{
this.consolas = res;
}, (err: any) =>{
console.log(err);
});
}
}
Angular Material
################
Angular material es una librería de estilos muy útil en ángular.
Preparación
***********
* Instalar angular material: ``ng add @angular/material``
* Crear módulo para separar la lógica de angular material: ``ng generate module material``
* Cargar modulo **material.module.ts** en **app.material.ts**:
.. code-block:: typescript
:linenos:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// importamos el módulo:
import { MaterialModule } from './material/material.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MaterialModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Uso de componentes
******************
* Editar modulo **material.module.ts** para cargar componentes de angular material y exportarlos:
.. code-block:: typescript
:linenos:
import { NgModule } from '@angular/core';
// cargamos el modulo del elemento a usar de angular material:
import {MatToolbarModule} from '@angular/material/toolbar';
@NgModule({
declarations: [],
imports: [ // se importa y se exporta:
MatToolbarModule
],
exports: [
MatToolbarModule
]
})
export class MaterialModule { }
* En cualquier template se puede cargar el componente añadido, en este caso **MatToolbarModule**:
.. code-block::
:linenos:
Taller angular
.. note::
En la página https://material.angular.io/components/categories se pueden ver los distintos componentes que pueden añadirse.