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 serve

Estructura 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 + 1

Pará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: 6

Destructuring 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 serve

Mó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 mimod

Estará 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 --export

Se 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.native

Styles:

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-info

Lazy 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, 3

Error: 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-store
npm 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 instance

Testing 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-coverage

Podemos probar nuestros componentes con el cli

o prueba generando informe de cobertura

Made with Slides.com