01. Introducción a Angular
Vamos a embarcarnos en esta aventura
¡Un poco! :)
Referent Frontend Developer en Sngular.
Llevo más de 10 años en esta industria, me encanta enseñar y ayudar a construir este nuevo ahora.
Primer paso: Explicación teórica del tema.
Segundo paso: Explicación práctica con la aplicación.
Tercer paso: Resolución de dudas.
Cuarto paso: Ejercicio práctico.
Nuestro proyecto - light theme
Nuestro proyecto - dark theme
Un framework creado por Google para facilitar las creaciones de SPAs.
No siempre fue el Angular que conocemos...
Un superset de Javascript creado por Microsoft
Una librería de programación reactiva para simplificar el código asíncrono a través de observers
npm install -g @angular/cli
ng new my-dream-app
cd my-dream-app
ng serve// app.component.ts
import { Component } from "@angular/core";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
title = "Hola Mundo";
}
// app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<div>
<h1>
{{ title }}!
</h1>
<img
width="300"
alt="Angular Logo"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
/>
</div>
02. ECMAScript6
let declara una variable de ámbito local.
El ámbito de un let es a nivel de bloques.
let miVariable = "Mi variable let"const declara una "variable" inmutable
const miConstante = "Mi constante inmutable"//ES5
(function() {
console.log(x); // x not defined yet.
if(true) {
var x = "hello world";
}
console.log(x);
// Paints "hello world", because "var" make it global
})();
//ES6
(function() {
if(true) {
let x = "hello world";
}
console.log(x);
//Error, because x has been defined inside the if scope
})();// BEFORE
var self = this;
function () { return self.quantity + 1; }
// AFTER
() => { return this.quantity + 1; }
() => this.quantity + 1// ES5
function test(x, y) {
if(x == null) {
x = 5;
}
...
}
// ES6
function test(x = 5, y) {
...
}Podemos definir valores por defecto en nuestros parámetros.
Nos permite usar expresiones y sintaxis multilinea dentro de nuestros strings
// ES5
var x = 'Mr.' + name;
// ES6
let x = `Mr.${name}`;
class Persona {
constructor(nombre, apellido, edad) {
this.nombre = nombre;
this.apellido = apellido;
this.edad = edad;
}
getName() {
return `Hola! me llamo ${this.nombre} ${this.apellido}`
}
setEdad(edad) {
this.edad = edad;
}
}ES6 nos permite agregar clases!
Nos permite importar nuestros módulos más fácilmente.
StringValidator.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
App.ts
import { StringValidator } from "./StringValidator";
// import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
// import * as validator from "./StringValidator";
let myValidator = new StringValidator();Permiten propagar y agregar nuestros valores dentro de otros.
const miArray = [1, 2 ,3];
const miSegundoArray = [...miArray, 4, 5, 6];
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));
// expected output: 6
console.log(sum.apply(null, numbers));
// expected output: 6let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// expected output: 10
console.log(b);
// expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// expected output: Array [30,40,50]
Expresión que permite desempacar valores de arrays o de objetos en distintas variables.
Podemos definir métodos dentro de nuestros objetos de forma más fácil.
// ES5
obj = {
foo: function (a, b) {
…
},
bar: function (x, y) {
…
}
};
// ES6
obj = {
foo(a, b) {
…
},
bar(x, y) {
…
}
};03. Typescript
Un superset de Javascript desarrollado por Miscrosoft
Le da super poderes a nuestro Javascript!
Misma sintaxis,
tipado fuerte.
No tengas miedo.
let test:string = 'Hello';
console.log(test);
// output: 'Hello'class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
let message:string = `Hello, ${this.name}`;
return message;
}
}Podemos crear código más robusto y enfocado a la POO
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
let message:string = `Hello, ${this.name}`;
return message;
}
}class Person {
constructor(name) {
this.name = name;
}
greet() {
let message = `Hello, ${this.name}`;
return message;
}
}Booleans
Numbers
String
Arrays
let isDone: boolean = false;let decimal: number = 22;
let hex: decimal = 0xf00d;
let binary: decimal = 0b1010;
let octal: decimal = 0o744;let color: string = 'Red';let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];Tuple
Enum
Any
both
// Declarar una tupla
let x: [string, number];
// Inicializando la tupla
x = ["hello", 10]; // Bien!
x = [10, "hello"]; // Mal :(enum Color {Red, Green, Blue};
let c: Color = Color.Green;let notSure: any = "Cualquiera!";
notSure = 45;
notSure = false;let stringNumber: string|number = "Puedo ser un string o un numero";
stringNumber = 10;Permite utilizar tipados en los parámetros de entrada y salida
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y };type Foo = {
readonly bar: number;
readonly bas: number;
}
// Initialization is okay
let foo: Foo = { bar: 123, bas: 456 };
// Mutation is not
foo.bar = 456; // Error: Left-hand side of assignment expression cannot be a constant or a read-only property
class Foo {
readonly bar = 1; // OK
readonly baz: string;
constructor() {
this.baz = "hello"; // OK
}
}Nos permite crear valores de solo lectura
Especifica métodos y propiedades sin implementación. Útil para requerir una estructura concreta en un método.
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label)
}
let myObj = {size: 10, label: "Size 10 object"};
printLabel(myObj);interface SquareConfig {
color?: string;
width?: number;
}Podemos crear una clase más grande usando clases más pequeñas. Útil para limpiar la estructura y el código, también para mejorar los test.
// Disposable mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}
}
// Activable mixin
class Activable {
isActivable: boolean;
activate() {
this.isActivable = true;
}
deactivate() {
this.isActivable = false;
}
}
// Class to combine the two mixins
class SmartObject implements Disposable, Activable {
constructor() {}
// Disposable
isDisposed: boolean = false;
dispose: () => void;
// Activable
isActivable: boolean = false;
activate: () => void;
deactivate: () => void;
}Decorators usan la @expression, dónde la expresión sera evaluada a una función que se llamará con la información declarada en el decorador.
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}Archivo de configuración de nuestro Typescript
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist"
},
"files": [
"src/Person.ts"
]
}04. Fundamentos de Angular
Los comandos para la instalación son los siguientes:
npm istall -g @angular/cli
ng new mi-app
cd mi-app
ng serveContienen todos los servicios y componentes necesarios para la aplicación
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
declarations: [ AppComponent ],
imports: [ BrowserModule ],
providers: [ Logger ],
exports: [ AppComponent ],
bootstrap: [AppComponent]
})
export class AppModule {}
Para crear un nuevo módulo en Angular, utilizamos el siguiente comando en el CLI:
ng generate module mimodEstará localizado en mimod/mimod.module.ts
Para crear un componente pasamos un decorador @Component y los mixins necesarios.
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'ab-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'] })
export class AppComponent implements OnInit, OnDestroy {
title = 'Mi primera App en Angular';
constructor() {
console.log('Hola constructor');
}
ngOnInit() {
console.log('Iniciando componente...');
}
ngOnDestroy() {
console.log('Componente completado');
}
}
El significado de las partes del componente es el siguiente:
Una vez definido tu componente, lo puedes utilizar en la vista. El archivo index.html sería:
<body>
<ab-root></ab-root>
</body>Desde el CLI de Angular puedes hacerlo utilizando la siguiente sintaxis:
ng generate component mimod/header --exportSe genera el archivo header.component.ts, en donde se permite la exportación del componente.
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'ab-header',
templateUrl: './header.component.html',
styles: []
})
export class HeaderComponent implements OnInit, OnDestroy {
constructor() {}
ngOnInit() {}
ngOnDestroy() {}
}
Para agregar un formulario en forma fácil, podemos hacerlo de alguna de las siguientes plantillas:
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-reactive-pais-residencia',
template: ` País de Residencia: <input type="text" [formControl]="paisResidenciaControl" />`
})
export class PaisResidenciaComponent {
paisResidenciaControl = new FormControl('');
}
import { Component } from '@angular/core';
@Component({
selector: 'app-template-pais-residencia',
template: ` Favorite Color: <input type="text" [(ngModel)]="paisResidencia">`
})
export class PaisResidenciaComponent {
paisResidencia = '';
}
05. Componentes
Componentes: partes visuales y lógicas fundamentales para construir la vista
@Component({
selector: 'my-component',
template: `<div>
Hello my name is {{name}}.
<button (click)="sayMyName()">Say my name</button>
</div>`
})
export class MyComponent {
constructor() {
this.name = 'Max'
}
sayMyName() {
console.log('My name is', this.name)
}
}
import { Component } from '@angular/core';
import { MyService } from './MyService';
@Component({
selector: 'my-component',
template: `<div>
Hello my name is {{name}}
</div>`,
providers: [ MyService ]
})
export class MyComponent {
constructor() {
this.name = 'Max'
}
sayMyName() {
console.log('My name is', this.name)
}
}
Importa el decorador component
de @angular/core
Especifica propiedades template, selector...
Controlador de componente
Funciones del componente
import { Component } from '@angular/core';
@Component({
selector: 'my-component',
template: `<div>
Hello my name is {{name}}
</div>`
})
export class MyComponent {
constructor() {
this.name = 'Max'
}
sayMyName() {
console.log('My name is', this.name)
}
}
<body>
<div>
<my-component></my-component>
</div>
</body> selector: 'my-component' template: '<h1>Hello</h1>',
templateUrl: './myComponent.html'Selector:
Especifica el tag personalizado del componente. Será usado cuando se llame a <my-component> desde el DOM
Template:
Especifica la plantilla HTML que se verá en el navegador.
Se puede especificar una plantilla externa con templateUrl
styles: `.test { color: red; }`,
stylesUrl: './myComponent.css' import ViewEncapsulation from @angular/core
...
encapsulation: ViewEncapsulation.nativeStyles:
Especifica los estilos que aplicarán a nuestro componente. Los CSS externos se aplican no stylesUrl.
Los CSS son encapsulados por defecto con una estrategia emulada. Se puede cambiar la configuración por alguna de las siguientes:
export class PeekABoo implements OnInit {
constructor(private logger: LoggerService) { }
// implement OnInit's `ngOnInit` method
ngOnInit() { this.logIt(`OnInit`); }
logIt(msg: string) {
this.logger.log(`#${nextId++} ${msg}`);
}
}Podemos controlar controlar ciclo de vida de un componente a través de diferentes metodos.
ngOnInit
Se inicializa después de mostrar las propiedades enlazadas y establecer las propiedades de entrada en la directiva/componente.
ngAfterContentIni
Se inicializa después de que Angular proyecte contenido externo en la vista del componente
ngAfterViewInit
Responde después de que Angular inicialice la vista del componente y de sus hijos.
ngOnDestroy
Empieza justo antes de que Angular destruya la directiva/componente. Cancele la suscripción de observables y desconecte los controladores de eventos para evitar pérdidas de memoria.
Angular component
Data/Class
Binding
Events
my-component
Accede al contexto de Angular con []
<img [src] = "heroImageUrl">Renderiza información con {{}}
<h1> {{ title }} </h1>Escucha eventos con ()
<button (click)="myFunc()">Define propiedades que pueden ser agregadas desde el padre
import { Component, Input } from '@angular/core';
@Component({
selector: 'hero-child',
template: `<p>I am at your service, {{masterName}}.</p>`
})
export class HeroChildComponent {
@Input() name: string;
@Input('master') masterName: string;
}Los input pueden tener un input alias
<hero-child [master]="heroName"></hero-child>Define y envía eventos desde hijo al padre
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'my-voter',
template: `<button (click)="vote(true)">Agree</button>`
})
export class VoterComponent {
@Output() onVoted = new EventEmitter<boolean>();
vote(agreed: boolean) {
this.onVoted.emit(agreed);
}
}<my-voter (onVoted)="myFunction"></my-voter>$ npm install -g @angular/cli$ ng new test-app$ cd test-app$ ng serve$ ng g component my-new-component06. Templating
Data binding
La forma básica de usar data binding. Podemos usar expresiones o llamar métodos de la clase controladora.
<p>The sum of 1 + 1 is {{1+1}}</p>
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
Data binding
Cosas que no podemos hacer
Data binding
PROP="TEXT"
Tiene una propiedad text
[PROP]="TEXT"
Tiene una el valor de la propiedad text del contexto
Data binding
Rápida ejecución
No repercute en el rendimiento de la aplicación
No hay efectos secundarios
Tiene una el valor de la propiedad text del contexto
Simplicidad
Separa la lógica de la vista
No solo click
Podemos escuchar cualquier evento creado por el navegador o los desarrolladores en los hijos del componente
<button (click)="readRainbow($event)"> Bindeo de clases condicional
En template
O como un objeto de classes
<div [class.my-class]="isActive"> Hello </div>
<div [ngClass]="{active: isActive}"> Hello </div>setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
saveable: this.canSave,
modified: !this.isUnchanged,
special: this.isSpecial
};
}
<div [ngClass]="currentClasses"></div>Bindeo condicional de estilos
En template
O como un objeto de classes
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
This div is x-large or smaller.
</div>setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}Directivas estructurales
*ngIf
Ocultar un elemento es un poco diferente que remover un elemento con ngIf
*ngFor
<div *ngIf="errorCount > 0" class="error">
{{errorCount}} errors detected
</div>
<hero-detail *ngIf="isActive"></hero-detail>
<for-example *ngFor="let episode of episodes" [episode]="episode">
{{episode.title}}
</for-example>Directivas estructurales
*ngSwitch
<div [ngSwitch]="hero?.emotion">
<happy-hero *ngSwitchCase="'happy'" [hero]="hero"></happy-hero>
<sad-hero *ngSwitchCase="'sad'" [hero]="hero"></sad-hero>
<confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
<unknown-hero *ngSwitchDefault [hero]="hero"></unknown-hero>
</div>07. Servicios e inyectables
Injectables: declara servicios y valores que son usados en los componentes.
import { HEROES } from './mock-heroes';
@Injectable()
export class HeroService {
getHeroes(): Hero[] {
return HEROES;
}
}// Component
constructor(private heroService: HeroService) { }Angular almacena la instancia del servicio inyectable.
Reduce la información que necesita una dependencia para simplificar el componente
import { Component } from '@angular/core';
import { Hamburger } from '../services/hamburger';
@Component({
selector: 'app',
template: `Bun Type: {{ bunType }}`
})
export class App {
bunType: string;
constructor(h: Hamburger) {
this.bunType = h.bun.type;
}
}Puede usar un alias para actualizar el provider evitando cambiarlo en la aplicación
providers: [{ provide:Logger, useClass: TimestampLogger}]Podemos utilizar el reducer @optional para no hacer que una dependencia sea crítica
import { Optional } from '@angular/core';
class AnotherService{
constructor(@Optional() private logger: Logger) {
if (this.logger) {
this.logger.log("I can log!!");
}
}
}Muy útil para usar dependencias de desarrollo sin modificar el código
HTTP Service
import { Http } from '@angular/http';
...
constructor (private http: HttpClient) {}
getHeroes (): Promise<Hero[]> {
return this.http.get(this.heroesUrl)
.toPromise();
}
// OR WITH OBSERVABLES
getHeroes (): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
}¿Promesas u observables?
¿Promesas u observables?
Los observables son preferibles a las promesas ya que estas proveen las características de las promesas y más.
No importa si quieres manejas uno o más eventos.
Los observables tienen otra ventaja, son cancelables.
¿Cómo funcionan los observables?
Un observa emite elementos o notificaciones a sus observadores cuando se modifica el elemento observado.
import { Observable } from 'rxjs/Observable';
// Create the observable
const data = new Observable(observer => {
setTimeout(function() {
observer.next(40);
}, 2000
});
....
// Subscribe the observable
data.subscribe(value => {
console.log(value);
});08. Módulos
Module: declara componentes, directivas, proveedores, servicios o esquemas para modularizar, mantener y escalar mejor nuestra aplicaciṕn
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }Todas las apps de Angular tienen al menos un módulo. Convencionalmente llamado AppModule
declarations: las clases de vista que pertenecen a este módulo. Angular tiene tres tipos de clases de vista: componentes, directivas y canalizaciones.
exports: subconjunto de declaraciones que deberían ser visibles y utilizables en las plantillas de componentes de otros módulos.
imports: otros módulos cuyas clases exportadas son necesarias por las plantillas de componentes declaradas en este módulo.
providers: creadores de servicios que este módulo aporta a la colección global de servicios; se vuelven accesibles en todas las partes de la aplicación.
Por cada proveedor se crea una nueva instancia de la dependencia.
bootstrap: la vista principal de la aplicación, denominada componente raíz, que aloja todas las demás vistas de la aplicación. Solo el módulo raíz debe establecer esta propiedad de arranque. (Solo para el módulo principal).
09. Routing
Sistema de rutas común
Múltiples páginas
Carga los recursos en distintas páginas.
Recursos
Sección
Recursos
Sección
Recursos
Sección
Single page applications
Una página
Carga los recursos en una página.
Sección
Recursos
Sección
Sección
Sistema de rutas SPAs
Una página
Trae recursos a demanda
Sección 3
Recursos
Sección 1
Sección 2
Trae los recursos críticos.
Primero: Puedes agregar el base tag
Agregua la etiqueta base a su index.html para indicarle a su aplicación angular dónde buscar recursos estáticos
<base href="/">Segundo: crear el objeto de rutas.
Podemos importar la interfaz de Routes para definir las rutas. ¡El orden importa!
import { RouterModule, Routes } from '@angular/router';
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'hero/:id', component: HeroDetailComponent },
{
path: 'heroes',
component: HeroListComponent,
data: { title: 'Heroes List' }
},
{ path: '',
redirectTo: '/heroes',
pathMatch: 'full'
},
{ path: '**', component: PageNotFoundComponent }
];Cada ruta asigna una URL a un componente. No hay que añadir barras al inicio del path. El enrutador analiza y crea la URL final, lo que le permite utilizar rutas relativas y absolutas al navegar entre las vistas.
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'hero/:id', component: HeroDetailComponent },El: id en la ruta es un token para un parámetro de ruta. En una URL /hero/42, "42" es el valor del parámetro id.
La data almacena datos asociados con la ruta. Es accesible dentro de cada ruta activada. Úsalo para guardar títulos de página, texto de ruta de navegación y otros datos estáticos de solo lectura.
{
path: 'heroes',
component: HeroListComponent,
data: { title: 'Heroes List' }
},
{ path: '',
redirectTo: '/heroes',
pathMatch: 'full'
},
{ path: '**', component: PageNotFoundComponent }La ruta vacía representa la ruta predeterminada para la aplicación, el lugar a donde ir cuando la ruta en la URL está vacía, como suele ser al principio.
Podemos usar un objeto secundario para agregar nuevas subrutas. Esas rutas se mostrarán en el componente "padre"
<router-outlet> </router-outlet>
{
path: 'heroes',
component: HeroListComponent,
data: { title: 'Heroes List' },
children: {
path: 'Test',
component: TestComponent
}
}El enrutador seleccionado se mostrará en la etiqueta <router-outlet> del "componente de arranque" en el componente raíz, o el componente padre si estamos usando rutas "secundarias".
<router-outlet></router-outlet>Y finalmente, agregamos el objeto de rutas a nuestro módulo, y podemos definir un "bootstrap" para contener nuestros componentes
const routes: Routes = [
{ path: 'component-one', component: ComponentOne },
{ path: 'component-two', component: ComponentTwo }
];
export const routing = RouterModule.forRoot(routes);
@NgModule({
...
imports: [
BrowserModule,
routing
],
bootstrap: [ AppComponent ]
...
}Ahora, podemos usar la directiva routerLink en nuestros elementos a para establecer la URL actual
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>Podemos usar el routerLinkActive para decirle a Angular que agregue una clase activa por sí mismo
PENSEMOS EN LA MODULARIZACIÓN
cmp
Módulo
INJ
Rutas
cmp
cmp
cmp
Módulo
INJ
Rutas
cmp
cmp
Módulo 1
Módulo 2
Módulo 3
Creemos un nuevo módulo en nuestra aplicación y un nuevo componente para el nuevo módulo.
$ ng g module lazy
$ ng g component lazy-infoElimina el componente lazy-info de las declaraciones de app.module
{
path: 'lazy',
loadChildren: () =>
import('./lazy/lazy.module').then((m) => m.LazyModule),
},Modifique app.routes.ts para agregar una nueva ruta con "loadChildren"
Ahora creemos un lazy.routes.ts en el directorio del módulo lazy con las rutas relativas
import {Routes} from "@angular/router";
import {LazyInfoComponent} from "../lazy-info/lazy-info.component";
export const lazyRoutes: Routes = [
{
path: '',
component: LazyInfoComponent
}
]
E impórtelos a nuestro nuevo módulo. ¡Pero CUIDADO!, ahora usaremos la función forChild
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {LazyInfoComponent} from "../lazy-info/lazy-info.component";
import {RouterModule} from "@angular/router";
import {lazyRoutes} from "./lazy.routes";
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(lazyRoutes)
],
declarations: [
LazyInfoComponent
]
})
export class LazyModule { }
Con los pasos anteriores, tendremos una aplicación cargando solo los recursos críticos en la primera carga, y los módulos de funciones cargando por solicitud
Podemos usar preloadingStrategy para precargar módulos
@NgModule({
bootstrap: [TestComponent],
imports: [RouterModule.forRoot(ROUTES,
{preloadingStrategy: PreloadAllModules})]
})
class TestModule {}¡Podemos crear estrategias personalizadas de precarga!
[
{
path: 'moduleA',
loadChildren: './moduleA.module',
data: {preload: true}
}
]export class PreloadSelectedModulesList implements PreloadingStrategy {
preload(route: Route, load: Function): Observable<any> {
return route.data && route.data.preload ? load() : of(null);
}
}Cualquier usuario puede navegar donde quiera por nuestra web y nuestros módulos. Pero tal vez:
Para manejar esos escenarios, podemos usar guardias. Estos mecanismos nos permiten "hacer cosas" antes de que se cargue el estado, como evitar la carga del módulo en función del rol del usuario u obtener algunos datos antes de que se inicialice el componente.
CanActivate se ejecuta para mediar en una navegación. Puede impedir la navegación si el guardia devuelve falso.
CanDeactivate similar al anterior pero se ejecuta en el estado de navegación "ausente". Útil para anular la suscripción de observables o dejar de ver ciertas variables
Resolve ejecuta una función para obtener algunos datos, requeridos por el componente de carga.
CanLoad a ejecuta una función para decidir si el módulo asíncrono debe cargarse o no.
CanActivate
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate() {
console.log('AuthGuard#canActivate called');
return true;
}
}import { AuthGuard } from '../auth-guard.service';
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard]
}
]Resolver
import { Injectable } from '@angular/core';
import { Router, Resolve, RouterStateSnapshot,
ActivatedRouteSnapshot } from '@angular/router';
import { Crisis, CrisisService } from './crisis.service';
@Injectable()
export class CrisisDetailResolver implements Resolve<Crisis> {
constructor(private cs: CrisisService, private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Crisis> {
let id = route.params['id'];
// get data from a service
return this.cs.getCrisis(id).then(crisis => {
if (crisis) {
return crisis;
} else { // id not found
this.router.navigate(['/crisis-center']);
return null;
}
});
}
}Resolver
// On my component
ngOnInit() {
this.route.data
.subscribe((data: { crisis: Crisis }) => {
this.editName = data.crisis.name;
this.crisis = data.crisis;
});
}10. Formularios
El módulo Angular forms nos permite manejar y adjuntar fácilmente el modelo de nuestros inputs
<label for="name">Name</label>
<input type="text" id="name" required [(ngModel)]="model.name" name="name">
ngModel, sincroniza nuestro modelo con los datos ingresados. Bajo el capó, Angular está creando un enlace de datos bidireccional, utilizando la notación "Banana-in-a-box" [()]
<form #heroForm="ngForm">
<label for="name">Name</label>
<input type="text" id="name" required[(ngModel)]="myModel.name" name="name">
<button type="submit">Submit</button>
</form>El atributo name siempre es obligatorio, no solo por a11y.Angular lo usa en el enlace del modelo.
Podemos manejar un objeto con todos los datos del formulario.
<form>
<input [(ngModel)]="form.name" name="name">
<input [(ngModel)]="form.mail" name="mail">
</form>Podemos crear referencias al formulario completo para validarlo completamente
#loginForm = "ngForm"
<form #loginForm="ngForm">
<input name="name" required [(ngModel)]="form.name">
<input name="password" type="password" required [(ngModel)]="form.password">
<button type="submit"></button>
</form>
<p *ngIf="!loginForm.valid"> Fill all the fields</p>Usa (ngSubmit) para manejar el envío del formulario
<form #loginForm="ngForm" (ngSubmit)="login(loginForm.value)">
<input name="name" required [(ngModel)]="form.name" >
<input name="password" type="password" required [(ngModel)]="form.password">
<button type="submit"></button>
</form>
<p *ngIf="!loginForm.valid"> Fill all the fields</p>Cuando se usa ngModel en un input, Angular modifica automáticamente el atributo className del elemento, agregando lo siguiente según el estado del elemento
Podemos utilizar condiciones para mostrar mensajes de error.
<input type="text" id="name"
required
[(ngModel)]="model.name"
name="name"
#name="ngModel" >
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
Name is required
</div>Y podemos agregar un ngSubmit al formulario, usando una acción personalizada definida en nuestra clase
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">Ahora, el formulario está asociado a heroForm var, lo que nos permite restablecerlo (heroForm.reset ()), validar (heroForm.valid) o acceder a (heroForm)
Con los formularios reactivos, creas un árbol de objetos de control de Angular form en la clase y los vincula a los elementos de control de forma nativa en la plantilla.
Crea y manipula objetos de control del formulario en la clase. Como la clase tiene acceso inmediato tanto al modelo de datos como a la estructura de control, puede insertar valores del modelo de datos en los controles del formulario y extraer los valores cambiados por el usuario.
¡Importante!
Tenemos que importar el módulo
ReactiveFormsModule
FormControl rastrea el valor y el estado de validez de un control de formulario individual. Corresponde a un control de formulario HTML como un cuadro de entrada o un selector.
FormGroup rastrea el valor y el estado de validez de un grupo. Las propiedades del grupo incluyen sus controles secundarios. El formulario de nivel superior en su componente es un FormGroup.
Imagina que queremos validar un nuevo usuario
export interface User {
name: string; // required with minimum 5 chracters
address?: {
street?: string; // required
postcode?: string;
}
}Podemos usar FormGroup y FormControl para crear validadores y enlazar a los formularios
this.myForm = new FormGroup({
name: new FormControl('', [<any>Validators.required, <any>Validators.minLength(5)]),
address: new FormGroup({
street: new FormControl('', <any>Validators.required),
postcode: new FormControl('8000')
})
});O use FormBuilder de la manera "corta"
constructor(private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
name: ['', [<any>Validators.required, <any>Validators.minLength(5)]],
address: this.formBuilder.group({
street: ['', <any>Validators.required],
postcode: ['']
})
});
}O crea validadores personalizados
import { FormControl } from '@angular/forms';
function validateEmail(c: FormControl) {
let EMAIL_REGEXP = ...
return EMAIL_REGEXP.test(c.value) ? null : {
validateEmail: {
valid: false
}
};
}11. RxJS & NgRx
RxJs es una librería reactiva. El port a JavaScript de la librería Reactive Extensions que ha sido portada a numerosos lenguajes de programación y que toma lo mejor de fuentes tan diversas como la programación funcional, el patrón Observer o el patrón iterador
"Lodash for asynchronous data"
Es programación orientada al manejo de streams de datos asíncronos y la propagación del cambio.
Un stream es un flujo de datos y tradicionalmente los streams han estado ligados a operaciones lectura/escritura de ficheros o querys a base de datos. En RxJs, no es muy diferente ya que sea cual sea el origen de la información, será tratada como un stream.
const arrayStream$ = Rx.Observable.from([10,20,30]);
// >> 10, 20, 30"todo es un stream" por lo que con RxJs, cualquier información será un stream. Eventos del ratón, Arrays, rangos de números, promesas, etc.
//Una letra es un stream
const letterStream$ = Rx.Observable.of(‘A’);
// Un rango de números es un stream
const rangeStream$ = Rx.Observable.range(1,8);
// Los valores de un Array son un stream
const arrayStream$ = Rx.Observable.from([1,2,3,4]);
// Valores emitidos cada 100 ms son un stream
const intervalStream$ = Rx.Observable.interval(100);
// Cualquier tipo de evento del ratón es un stream
const clicksStream$ = Rx.Observable.fromEvent(document, 'click');
// La respuesta a un servicio basada en una promesa es un stream
const promiseStream$ = Rx.Observable.fromPromise(fetch(’/products')); Los streams están representados por "secuencias observables" u Observables, por lo que en RxJs todo es un Observable
El patrón Observer define un productor de información, nuestro stream, que en RxJs es un Observable, y un consumidor de la misma, que sería el Observer.
El Observable es nuestro stream y que nos sirve para prácticamente todo: eventos del ratón, rangos de números, promesas, etc.
Observer es un objeto con 3 métodos que recibe información del Observable.
//Observable
const myObservable$ = Rx.Observable.from([1,2,3]);
//Observer
const myObserver = {
next: x => console.log(`Observer next value: ${x}`),
error: err => console.error(`Observer error value: ${x}`),
complete: () => console.log(`Observer complete notification`),
};
myObservable$.subscribe(myObserver); Cada método cumple una función y con cada uno recibiremos distintos tipos de notificaciones del Observable.
El Observer no devolverá ningún valor hasta que se active la comunicación. Nada se ejecutará hasta que establezcamos una subscripción.
const arrayStream$ = Rx.Observable.from([1,2,3]);
arrayStream$
.subscribe(
next => console.log(next),
err => console.log(err),
() => console.log('completed!')
); Se puede subscribir pasando funciones como params. RxJs asignará cada callback al respectivo método del Observer, siguiendo el orden en el que son pasados.
Next: Es el primer y más importante callback en la subscripción, el resto son opcionales. Notifica un valor del stream emitido por el Observable.
Rx.Observable.from([1,2,3])
.subscribe(next => console.log(next)); // -> 1, 2, 3Error: Se ejecuta cuando se ha producido un error o excepción.
Rx.Observable.from([1,2,3])
.subscribe(
next => console.log(next),
err => console.log(err) // Se ha producido un error
);Complete: Es una notificación sin valor y se emite cuando el stream ha finalizado
Rx.Observable.from([1,2,3])
.subscribe(
next => console.log(next),
err => console.log(err),
() => console.log('completed!') // Se ejecuta cuando finaliza el stream, después del (3)
);@ngrx/store es un contenedor de estado controlado diseñado para ayudar a escribir aplicaciones consistentes y de alto rendimiento sobre Angular. Principios básicos:
$ ng add @ngrx-storenpm install @ngrx/store --save//main.state.ts
export interface State {
counter: number;
};
export const intitialState: State = {
counter: 10
};Dentro de la carpeta de estado, creemos un nuevo archivo
import {ActionReducer, Action} from "@ngrx/store";
import {State, intitialState} from "../state/main-state";
export const mainStoreReducer: ActionReducer<State> = (state = intitialState, action: Action) => {
switch (action.type) {
case 'INCREMENT': {
return {
counter: state.counter + 1
}
}
case 'EVENT_FROM_EFFECT': {
return {
counter: 4
}
}
default: {
return state;
}
}
};El reducer es lo que captura y maneja las acciones que se envían desde sus componentes inteligentes (o desde sus reductores de acción), y también es lo que realmente restablece el estado a un estado nuevo y modificado.
//app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { mainStoreReducer } from "./state-management/reducers/main.reducer";
import { StoreModule } from "@ngrx/store";
const reducers = { counter: mainStoreReducer };
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
StoreModule.forRoot(reducers)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Component } from '@angular/core';
import { Store } from "@ngrx/store";
import { State } from "./state-management/state/main.state";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app works!';
constructor (private store:Store<State>) {
console.log('We have a store! ' + store);
}
}import { Component } from '@angular/core';
import { Store } from "@ngrx/store";
import { State } from "./state-management/state/main.state";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app works!';
data = '';
constructor (private store:Store<State>) {
store.select('counter')
.subscribe( (data:State )=> {
this.data = 'data is' + data;
});
}
}Leyendo los datos
import { Component } from '@angular/core';
import { Store } from "@ngrx/store";
import { State } from "./state-management/state/main.state";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app works!';
data = '';
constructor (private store:Store<State>) {
store.select('mainStoreReducer')
.subscribe( (data:State )=> {
this.data = 'data is ' + data;
});
}
public increment() {
this.store.dispatch({ type: 'INCREMENT' });
}
}
Llamando acciones del reducer
//app.component.html
<h1>
{{title}}
</h1>
<h2>
{{data}}
</h2>
<button (click)="increment()"> + 1 </button>
12. Testing
Las pruebas unitarias se refieren a la práctica de probar ciertas funciones y áreas, o unidades, de nuestro código. Esto nos da la capacidad de verificar que nuestras funciones funcionen como se esperaba. Es decir, para cualquier función y dado un conjunto de entradas, podemos determinar si la función está devolviendo los valores adecuados y manejará con gracia los fallos durante la ejecución, en caso de que se proporcionen entradas no válidas.
Testing framework
Javascript test runner
suite: colección de casos de prueba que están destinados a ser utilizados para probar un programa de software para mostrar que tiene un conjunto específico de comportamientos
spec: caso de prueba para una especificación concreta del componente / archivo a probar
Esta es una prueba básica con Jasmine
describe("AppComponent", function() {
var a;
it("a should be true", function() {
a = true;
expect(a).toBe(true);
});
});Angular recomienda esta pila para probar nuestros componentes y proporcionar algunas herramientas para ayudarnos a configurar nuestras dependencias en nuestras pruebas.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';TestBed: Crea un módulo de prueba de Angular, una clase @NgModule, que configura con el método configureTestingModule para producir el entorno del módulo para la clase que desea probar.
En efecto, separa el componente probado de su propio módulo de aplicación y lo vuelve a conectar a un módulo de prueba de Angular construido dinámicamente y diseñado específicamente para esta batería de pruebas.
TestBed
TestBed.configureTestingModule({
declarations: [ BannerComponent ], // declare the test component
});createComponent
Después de configurar TestBed, di que cree una instancia del componente bajo prueba. Usaremos TestBed.createComponent para crear una instancia de Component y devuelve un accesorio de prueba de componente.
El método createComponent cierra la instancia actual de TestBed para una mayor configuración
TestBed
El Fixture es un controlador del entorno de prueba que rodea al componente creado. Proporciona acceso a la instancia del componente en sí y al DebugElement, que es un identificador del elemento DOM del componente.
fixture = TestBed.createComponent(BannerComponent);
comp = fixture.componentInstance; // BannerComponent test instancePrueba básica con Angular
it('should increment number', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const elm = fixture.componentInstance;
expect(elm.value).toBe(5);
elm.testFuncion();
expect(elm.value).toBe(6);
}));La detección automática de cambios está deshabilitada en las pruebas de Angular por defecto, para dar al desarrollador más control sobre los cambios asíncronos y no controlados.
Prueba básica con Angular
import { ComponentFixtureAutoDetect } from '@angular/core/testing';
TestBed.configureTestingModule({
declarations: [ BannerComponent ],
providers: [
{ provide: ComponentFixtureAutoDetect, useValue: true }
]
})Podemos activar la detección automática importando ComponentFixtureAutoDetect y agregándolo a los proveedores
Si queremos probar un evento, un servicio asincrónico o una petición de servicio, podemos utilizar espías.
Un espía puede bloquear cualquier función y rastrear las llamadas a ella y todos los argumentos. Hay emparejadores especiales para interactuar con espías. El comparador toHaveBeenCalled devolverá true si se llamó al espía
Podemos usar un valor diferente para un servicio con el fin de probarlo.
let mockRouter = {
navigate: jasmine.createSpy('navigate')
};
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ NavToolComponent ],
providers: [
{ provide: Router, useValue: mockRouter }
]})
})
...
expect(mockRouter).toHaveBeenCallWith(['/home']);spyOn(callToService, 'getUsers').andReturn(fakeHttpPromise);
component.updateUser();
expect(callToService.getUsers).toHaveBeenCalled();
component.updateUser('admin');
expect(callToService.getUsers).toHaveBeenCalledWith('admin');
it('should emit on filter click', inject([], () => {
let valueAfterClick;
productListComp.itemClicked.subscribe(item => {
console.log(item);
valueAfterClick = item;
});
let el = fixture.debugElement.children[0];
let card = el.query(By.css('.global-position__personal')).nativeElement;
card.click();
expect(valueAfterClick).toBe('profile');
}));
Testing events
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {TeoLoginComponent} from './teo-login.component';
describe('TeoLoginComponent', () => {
let component: TeoLoginComponent;
let fixture: ComponentFixture<TeoLoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TeoLoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TeoLoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
$ ng test$ ng test --code-coveragePodemos probar nuestros componentes con el cli
o prueba generando informe de cobertura