Curso de Angular

01. Introducción a Angular
¡Bienvenidos a este curso!

Vamos a embarcarnos en esta aventura
Vamos a conocernos
¡Un poco! :)
Marcela Gotta
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.
¡Ahora ustedes!
¿Gustos? ¿Pasiones? ¿Experiencia?
Contenido del curso
- Introducción a Angular
- Fundamentos de Angular
- Typescript orientado a Angular
- Componentes y directivas
- Sintaxis y Modularización
- Servicios e inyectable
- Formularios
- Routing
- RxJS
- Testing
- Add-ons
- Referencias
Metodología
-
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
¿Qué es Angular?
Un framework creado por Google para facilitar las creaciones de SPAs.
Diferencias con AngularJS
No siempre fue el Angular que conocemos...
¿Qué es Typescript?
Un superset de Javascript creado por Microsoft
¿Qué es RxJS?
Una librería de programación reactiva para simplificar el código asíncrono a través de observers
Instalación de Angular
npm install -g @angular/cli
ng new my-dream-app
cd my-dream-app
ng serveEstructura en Angular

Dependencias en Angular

Hola Mundo en Angular
// 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>
¡Es hora de programar!

Curso de Angular

02. ECMAScript6
Let y const
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"Let y const
//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
})();Arrow functions
- Sintaxis más consistentes
- Comparte el this del ámbito del padre
// BEFORE
var self = this;
function () { return self.quantity + 1; }
// AFTER
() => { return this.quantity + 1; }
() => this.quantity + 1Parámetros por defecto
// 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.
Plantillas literales
Nos permite usar expresiones y sintaxis multilinea dentro de nuestros strings
// ES5
var x = 'Mr.' + name;
// ES6
let x = `Mr.${name}`;
Clases en Javascript
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!
Módulos JS
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();Operadores spread
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: 6Destructuring de datos
let 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.
Propiedades de objetos
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) {
…
}
};¡Es hora de programar!

Curso de Angular
03. Typescript
¿Qué es Typescript?
Un superset de Javascript desarrollado por Miscrosoft
Le da super poderes a nuestro Javascript!
¿Por qué Typescript?
- Superset de Javascript
- Tipado estático
- Transpilador de ECMASCRIPT6
- Orientado a objetos
- Detección de errores antes de arrancar
- Código más robusto
- Similar a tecnologías Backend
¿Por qué Typescript?
Misma sintaxis,
tipado fuerte.
No tengas miedo.
let test:string = 'Hello';
console.log(test);
// output: 'Hello'Programación Orientada a objetos
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
Typescript vs Javascript
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;
}
}Tipado en Typescript
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];Tipado en Typescript
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;Funciones en Typescript
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 };Propiedades Readonly
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
Interfaces en Typescript
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;
}Mixins en Clases
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;
}Decoradores en Typescript
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;
}
}tsconfig.json
Archivo de configuración de nuestro Typescript
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist"
},
"files": [
"src/Person.ts"
]
}¡Es hora de programar!

Curso de Angular

04. Fundamentos de Angular
Características de Angular
- Velocidad y rendimiento
- Estructura de directorios
- Comunidad de desarrollo
- Google está detrás de su desarrollo
Instalación de Angular CLI
Los comandos para la instalación son los siguientes:
npm istall -g @angular/cli
ng new mi-app
cd mi-app
ng serveMódulos en Angular
Contienen 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 {}
Módulos en Angular
- declarations: Array de componentes y directivas del módulo NgModule.
- imports: Arreglo de módulos, cuyas clases exportadas son requeridas por la plantilla de componentes del módulo NgModule.
- exports: Declaraciones que deben estar visibles en la plantilla de componentes de otros NgModules.
- providers: Creadores de servicios en donde el NgModule contibuye, accesibles desde cualquier lado en la aplicación.
- bootstrap: Vista principal o componente raíz, que aloja todas las demás vistas.
Creación de un módulo
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
Componentes de Angular
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');
}
}
Componentes de Angular
El significado de las partes del componente es el siguiente:
- selector: ‘ab-root’ – Etiqueta que define una propiedad que se puede utilizar como un componente HTML, es decir <ab-root></ab-root>.
- templateUrl: ‘./app.component.html’ – Ubicación del componente HTML que queremos utilizar.
- styleUrls: [‘./app.component.css’] – Array de estilos que vamos a utilizar para nuestro componente en Angular.
Componentes de Angular
Una vez definido tu componente, lo puedes utilizar en la vista. El archivo index.html sería:
<body>
<ab-root></ab-root>
</body>Creación de componentes
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() {}
}
Formularios en Angular
Para agregar un formulario en forma fácil, podemos hacerlo de alguna de las siguientes plantillas:
- Formularios Reactivos: Son formularios con acceso explícito en el objeto modelo, son explícitos y robustos, es decir: reutilizables, escalables y testeables.
- Formularios Dirigidos a Plantilla: Crean y modifican el objeto modelo. Son más sencillos de implementar, pero no se escalan muy bien. Se utilizan en mejor forma en casos simples como formularios de registro o de login.
Formularios Reactivos
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('');
}
Formularios de Plantilla
import { Component } from '@angular/core';
@Component({
selector: 'app-template-pais-residencia',
template: ` Favorite Color: <input type="text" [(ngModel)]="paisResidencia">`
})
export class PaisResidenciaComponent {
paisResidencia = '';
}
¡Es hora de programar!

Curso de Angular

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)
}
}
Partes de un componente
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)
}
}
Utilización un componente
<body>
<div>
<my-component></my-component>
</div>
</body> selector: 'my-component'Propiedades del componente
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'Propiedades del componente
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:
- Emulated
- Native
- None
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}`);
}
}Ciclo de vida
Podemos controlar controlar ciclo de vida de un componente a través de diferentes metodos.

Ciclo de vida
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
Ciclo de vida
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.
Flujo de datos

Flujo de datos
Angular component
Data/Class
Binding
Events
my-component
Sintaxis corta
Accede al contexto de Angular con []
<img [src] = "heroImageUrl">Renderiza información con {{}}
<h1> {{ title }} </h1>Escucha eventos con ()
<button (click)="myFunc()">Flujo de datos
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>Input
Flujo de datos
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>Output
Webpack

Angular CLI
$ npm install -g @angular/cli$ ng new test-app$ cd test-app$ ng serve$ ng g component my-new-component¡Es hora de programar!

Curso de Angular

06. Templating
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>
Templating
Data binding
Cosas que no podemos hacer
- Asignaciones (=, +=, -=, ...)
- new
- Encadenar expresiones con ; o ,
- Operadores de incremento y decremento (++ y --)
- No soporta los operadores bit a bit | y &
Templating
Data binding
PROP="TEXT"
Tiene una propiedad text
[PROP]="TEXT"
Tiene una el valor de la propiedad text del contexto
Templating
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
Eventos
No solo click
Podemos escuchar cualquier evento creado por el navegador o los desarrolladores en los hijos del componente
<button (click)="readRainbow($event)"> Eventos
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>Eventos
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'
};
}Eventos
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>Eventos
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>¡Es hora de programar!

Curso de Angular

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);
});¡Es hora de programar!

Curso de Angular

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).
Curso de Angular

09. Routing
Contexto de las rutas
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
Contexto de las rutas
Single page applications
Una página
Carga los recursos en una página.
Sección
Recursos
Sección
Sección
Contexto de las rutas
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.
Angular router
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="/">Angular router
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 }
];Angular router
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.
Angular router
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.
Angular router
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
}
}Angular router
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>Angular router
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 ]
...
}Angular router
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
Angular router
PENSEMOS EN LA MODULARIZACIÓN
cmp
Módulo
INJ
Rutas
cmp
cmp
Angular router
cmp
Módulo
INJ
Rutas
cmp
cmp
Módulo 1
Módulo 2
Módulo 3
Lazy load
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-infoLazy load
Elimina 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"
Lazy load
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
}
]
Lazy load
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 { }
Precarga de módulos
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
Precarga de módulos
Podemos usar preloadingStrategy para precargar módulos
@NgModule({
bootstrap: [TestComponent],
imports: [RouterModule.forRoot(ROUTES,
{preloadingStrategy: PreloadAllModules})]
})
class TestModule {}Precarga de módulos
¡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);
}
}Guards en rutas
Cualquier usuario puede navegar donde quiera por nuestra web y nuestros módulos. Pero tal vez:
- Quizás el usuario debe iniciar sesión (autenticarse) primero.
- Quizás debería buscar algunos datos antes de mostrar el componente de destino.
- Es posible que desee guardar los cambios pendientes antes de dejar un componente.
Guards en rutas
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.
Guards en rutas
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
Guards en rutas
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.
Guards en rutas
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]
}
]Guards en rutas
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;
}
});
}
}Guards en rutas
Resolver
// On my component
ngOnInit() {
this.route.data
.subscribe((data: { crisis: Crisis }) => {
this.editName = data.crisis.name;
this.crisis = data.crisis;
});
}¡Es hora de programar!

Curso de Angular

10. Formularios
Angular forms
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">
Angular forms
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.
Angular forms
Podemos manejar un objeto con todos los datos del formulario.
<form>
<input [(ngModel)]="form.name" name="name">
<input [(ngModel)]="form.mail" name="mail">
</form>Angular forms
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>Angular forms
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>Angular forms
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

Angular forms
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>Angular forms
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)
Formularios reactivos
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.
Formularios reactivos
¡Importante!
Tenemos que importar el módulo
ReactiveFormsModule
Formularios reactivos
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.
Formularios reactivos
Imagina que queremos validar un nuevo usuario
export interface User {
name: string; // required with minimum 5 chracters
address?: {
street?: string; // required
postcode?: string;
}
}Formularios reactivos
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')
})
});Formularios reactivos
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: ['']
})
});
}Formularios reactivos
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
}
};
}¡Es hora de programar!

Curso de Angular

11. RxJS & NgRx
¿Qué es RxJs?
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"
¿Qué es la programación reactiva?
Es programación orientada al manejo de streams de datos asíncronos y la propagación del cambio.
¿Qué es un stream?
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¿Qué es un stream?
"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
¿Qué 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.
¿Qué es un observer?
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.
¿Qué es un observer?
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, error y complete
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)
);¿Qué es NgRx?
@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:
- El estado es una estructura de datos única e inmutable
- Las acciones describen cambios de estado
- Las funciones puras llamadas reducers toman porciones de estado anteriores y la siguiente acción para calcular el nuevo estado
- Estado se accede con el Store, un observable de estado y un observador de acciones
¿Qué es NgRx?

Instalación de NgRx
$ ng add @ngrx-storenpm install @ngrx/store --save
Instalación de NgRx
//main.state.ts
export interface State {
counter: number;
};
export const intitialState: State = {
counter: 10
};Dentro de la carpeta de estado, creemos un nuevo archivo
¿Qué es el Reducer?
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.
Importando nuestro Store
//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 { }
Accediendo al 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!';
constructor (private store:Store<State>) {
console.log('We have a store! ' + store);
}
}Accediendo al 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
Accediendo al 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('mainStoreReducer')
.subscribe( (data:State )=> {
this.data = 'data is ' + data;
});
}
public increment() {
this.store.dispatch({ type: 'INCREMENT' });
}
}
Llamando acciones del reducer
Accediendo al Store
//app.component.html
<h1>
{{title}}
</h1>
<h2>
{{data}}
</h2>
<button (click)="increment()"> + 1 </button>
¡Es hora de programar!

Curso de Angular

12. Testing
Testing unitario
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 unitario
- Ayuda a identificar fallos en nuestra lógica
- Escribe el código "fácil de probar"
- Evitar futuras "interrupciones en la construcción"
- Simplifica la integración y la reutilización
- Puede funcionar como validación de documentos de funcionalidad
Testing unitario


Testing framework
Javascript test runner
Testing unitario - Jasmine
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
Testing unitario - Jasmine
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);
});
});Testing unitario - Jasmine
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';Testing unitario - Jasmine
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.
Testing unitario - Jasmine
TestBed
TestBed.configureTestingModule({
declarations: [ BannerComponent ], // declare the test component
});Testing unitario - Jasmine
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
Testing unitario - Jasmine
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 instanceTesting unitario - Jasmine
Prueba 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.
Testing unitario - Jasmine
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
Testing unitario - Jasmine
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
Testing unitario - Jasmine
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']);Testing unitario - Jasmine
spyOn(callToService, 'getUsers').andReturn(fakeHttpPromise);
component.updateUser();
expect(callToService.getUsers).toHaveBeenCalled();
component.updateUser('admin');
expect(callToService.getUsers).toHaveBeenCalledWith('admin');
Testing unitario - Jasmine
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
Testing unitario - Jasmine
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();
});
});
Testing unitario - Jasmine
$ ng test$ ng test --code-coveragePodemos probar nuestros componentes con el cli
o prueba generando informe de cobertura
Curso de Angular 11+
By Mars Gotta
Curso de Angular 11+
Estas son las diapositivas del curso de Angular que imparto :D
- 195