Tabla de Contenidos

Angular basics

Basics of basics

Glosario

Aplicación básica

Necesitamos tener una versión de Node.js superior a v5 y de npm superior a v3; para conocer qué versión tenemos de cada uno haremos node -v y npm -v.

Necesitamos los siguientes ficheros:

Para instalar los paquetes de package.json haremos:

Los comandos de npm útiles son:

Notes

Organization: All the files of the project are created inside a folder called app.

Para poder importar una clase, ésta primero se ha de expeortar.

TypeScript

Classes:

export class Hero {
  id: number;
  name: string;
}

Assigning an object to a property:

hero: Hero = {
  id: 1,
  name: 'Windstorm'
};

Una propiedad a la que se le asigna un array vacío:

missions: Mission[] = [];

Convertir objeto a JSON:

return JSON.stringify(this.model);

Listas

Encontrar un elemento en una lista:

getHero(id: number) {
  return this.getHeroes()
             .then(heroes => heroes.find(hero => hero.id === id));
}

Recoger unos elementos concretos:

 this.heroService.getHeroes()
      .then(heroes => this.heroes = heroes.slice(1, 5));

Elements

Components

Toda aplicación Angular tiene al menos un componente, el root component, que por convención es llamado AppComponent. Un componente es un bloque de código que controla una parte de la vista. Esta es la estructura básica de uno de ellos:

import { Component } from '@angular/core';
@Component({
  selector: 'my-app',
  template: '<h1>My First Angular 2 App</h1>'
})
export class AppComponent { }

Contiene: (1) imports para hacer referencia a lo que necesita. (2) Decorador @Component para indicar plantillas y propiedades “externas” del componente (metadata que indica como crearlo y usarlo). (3) Su clase.

En el decorador nos encontramos con el selector y la template. El primero es la tag que corresponderá al componente en contreto. El segundo, la template, especifica el HTML que se incrustará cuando tenga que aparecer.

Clase con propiedades

export class AppComponent {
  title = 'Tour of Heroes';
  hero = 'Windstorm';
}

Leer contenido desde la template

Template de una sola línea que lee la propiedad title y la hero:

template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'

Template de varias líneas (con el acento a la izquierda):

template:`
  <h1>{{title}}</h1>
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div><label>name: </label>{{hero.name}}</div>
  `

Añadir estilos en el componente

El decorador de componente también admite la propiedad styles:

styles: [`
  .selected {
    background-color: #CFD8DC !important;
    color: white;
  }
  .heroes {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
  }
  .heroes li {
    cursor: pointer;
    position: relative;
    left: 0;
    background-color: #EEE;
    margin: .5em;
    padding: .3em 0;
    height: 1.6em;
    border-radius: 4px;
  }
  .heroes li.selected:hover {
    background-color: #BBD8DC !important;
    color: white;
  }
  .heroes li:hover {
    color: #607D8B;
    background-color: #DDD;
    left: .1em;
  }
  .heroes .text {
    position: relative;
    top: -3px;
  }
  .heroes .badge {
    display: inline-block;
    font-size: small;
    color: white;
    padding: 0.8em 0.7em 0 0.7em;
    background-color: #607D8B;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -4px;
    height: 1.8em;
    margin-right: .8em;
    border-radius: 4px 0 0 4px;
  }
`]

También podemos hacerlo desde un fichero externo con:

styleUrls: ['app/dashboard.component.css']

Para añadir los estilos globales de toda la app simplemente añadiremos el link al index.html:

<link rel="stylesheet" href="styles.css">

Añadir propiedades de entrada

Para ello usamos el decorador @Input (que necesitamos importar) sobre la propiedad de la clase:

import { Component, Input } from '@angular/core';
import { Mission } from './mission';
 
@Component({
  selector: 'mission-detail',
  template: `
  <div *ngIf="mission">
  ...
  </div>
`
})
export class MissionDetailComponent {
    @Input()
    mission: Mission;
}

Luego, cuando la queramos usar en una template:

<mission-detail [mission]="selectedMission"></mission-detail>

:!: Recuerda que para que funcione has de incluir el nuevo selector como directiva en la lista de directivas de ese componente:

@Component({
  selector: 'newskid-app',
  template: `
  `,
  styles: [`
`],
 directives: [MissionDetailComponent]
})

Template en fichero

En vez de usar la propiedad template usaremos la propiedad templateUrl:

templateUrl: 'app/dashboard.component.html',

Templates for components

ngFor

Podemos indicar que cuando se haga click en un elemento…

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

… se llame a un método concreto de la clase:

export class AppComponent {
  title = 'Newskid missions';
  missions = MISSIONS;
  selectedMission: Mission;
 
  onSelect(mission: Mission) { 
    this.selectedMission = mission; 
  }
}

ngIf

Si existe el elemento….

<div *ngIf="selectedMission">
  <h2>{{selectedMission.name}} Edition</h2>
  <div><label>id: </label>{{selectedMission.id}}</div>
  <div>
    <label>name: </label><input [(ngModel)]="selectedMission.name" placeholder="name"/>
  </div>
</div>

Bootstrapping

Es importante añadir un finchero main.ts que contenga el bootstrapping de la app:

import { bootstrap }    from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
bootstrap(AppComponent);

Se ha de importar los dos elementos necesarios para lanzar la app:

  1. La función bootstrap.
  2. El componente root de la aplicación.

La función bootstrap viene de platform-browser-dynamic debido a que hay muchas formas de lanzar la app. Una de ellas, por ejemplo, es hacerlo desde el server.

El fichero index.html

Es el fichero que define la página web y que contiene la app. Se declaran las librerías JavaScript necesarias, la configuración para el SystemJS y la tag de la aplicación (en este caso my-app).

<!DOCTYPE html>
<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">
    <!-- 1. Load libraries -->
     <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>
  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

Services

Para hacer un servicio, una clase que se pueda instanciar automáticamente en el constructor, añadimos @Injectable() a la clase:

import { Injectable } from '@angular/core';
import { MISSIONS } from './mock-missions';
 
@Injectable()
export class MissionService {
    getMissions () {
        return Promise.resolve(MISSIONS);
    }
}

A partir de ahora puede ser usado en la clase:

@Component({
  selector: 'newskid-app',
  ...
 providers: [MissionService]
})
export class AppComponent implements OnInit {
...
  constructor(private missionService: MissionService) { }

Esto agrega una propiedad a la clase denominada missionService. Fíjate que también hay que añadirlo en el array de providers.

Interfaces

ngOnInit

import { OnInit } from '@angular/core';
 
export class AppComponent implements OnInit {
  ngOnInit() {
  }
}

Promises

Devolver\declarar una promise (función que hace algo en algún momento, que tarda algún tiempo y que, cuando le sale, devuelve algo):

getHeroes() {
  return Promise.resolve(HEROES);
}

Usar lo que devuelve (HEROES es un array):

getHeroes() {
  this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}

Routing

AppComponent únicamente debería manejar la navegación. El objeto router es el encargado de gestionarla y es una combinación del provideRouter, directivas y configuración.

Es necesario tener en el index.html:

<head>
  <base href="/">

Crearemos un fichero app.routes.ts donde añadiremos todas las rutas:

import { provideRouter, RouterConfig }  from '@angular/router';
import { HeroesComponent } from './heroes.component';
 
const routes: RouterConfig = [
  {
    path: 'heroes',
    component: HeroesComponent
  }
];
 
export const appRouterProviders = [
  provideRouter(routes)
];

La RouterConfig es un array de definiciones de ruta. Aquí únicamente tenemos una definición, pero podemos añadir más. Una difinición tiene un path (el path con el que hace match la URL, en este caso /heroes) y un componente (el componente que se crea en esa ruta).

El router ha de ser importado en el bootstrap:

import { bootstrap }    from '@angular/platform-browser-dynamic';
 
import { AppComponent } from './app.component';
import { appRouterProviders } from './app.routes';
 
bootstrap(AppComponent, [
  appRouterProviders
]);

Una de las directivas de router (ROUTER_DIRECTIVES) es <router-outlet>. Si lo añadimos en una template (ejemplo de app.component):

import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
import { HeroService }     from './hero.service';
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <a [routerLink]="['/heroes']">Heroes</a>
    <router-outlet></router-outlet>
  `,
  directives: [ROUTER_DIRECTIVES],
  providers: [
    HeroService
  ]
})
export class AppComponent {
  title = 'Tour of Heroes';
}

También podemos crear una redirección:

const routes: RouterConfig = [
  {
    path: 'missions',
    component: MissionsComponent
  },
  {
    path: '',
    redirectTo:'/dashboard',
    pathMatch: 'full'
  },
  {
    path: 'dashboard',
    component: DashboardComponent
  }
];

O parametrizar una ruta (por ejemplo /detail/11):

{
  path: 'detail/:id',
  component: HeroDetailComponent
},

Entonces tendremos que recoger el id dentro del componente donde se vaya a leer, para ello leemos de la url en el navegador:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Hero } from './hero';
import { HeroService } from './hero.service';
 
@Component({
  selector: 'my-hero-detail',
  templateUrl: 'app/hero-detail.component.html',
})
export class HeroDetailComponent implements OnInit, OnDestroy {
  hero: Hero;
  sub: any;
 
  constructor(
    private heroService: HeroService,
    private route: ActivatedRoute) {
  }
 
  ngOnInit() {
    this.sub = this.route.params.subscribe(params => {
      let id = +params['id']; /* convert id to int */
      this.heroService.getHero(id)
        .then(hero => this.hero = hero);
    });
  }
 
  ngOnDestroy() {
    this.sub.unsubscribe();
  }
 
  goBack() {
    window.history.back();
  }
}
  1. Necesitamos un observer de la url, para ello accedemos a la ActivatedRoute
  2. Nos aseguramos que nos subscrivimos a la ActivatedRoute en el ngOnInit y que en el ngOnDestroy nos desubscribimos.
  3. Necesitamos agregarlo al constructor
  4. En el ngOnInit nos subscribimos a los params para obtener la id de la ruta actual (ActivateRoute); luego se usará el servicio para obtener ese id concreto.

Http