Angular2 workshop

Gonzalo Ruiz de Villa

@gruizdevilla

https://goo.gl/74t6CF

AngularJS Madrid

La url de esta presentación se emitirá durante todo el workshop. 

https://goo.gl/74t6CF

'Angular 1 was a framework, Angular 2 is a platform' The father of Angular Misko Hevery

https://goo.gl/74t6CF

Web

Mobile Web

Android

iOS

Windows

Mac

Linux

https://goo.gl/74t6CF

Progressive Web Apps

https://goo.gl/74t6CF

  • pensado para construir aplicaciones del tamaño que sea
  • mobile first
  • rendimiento brutal
  • sencillo y expresivo
  • inmutabilidad
  • inyección de dependencias jerárquica
  • integración con webcomponents
  • i18n
  • server side rendering
  • web workers
  • aplicaciones nativas, de escritorio
  • PWA
  • Angular CLI

¿Por qué Angular 2?

https://goo.gl/74t6CF

Empecemos

https://goo.gl/74t6CF

1. Configuremos
el entorno

https://goo.gl/74t6CF

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  }
}

tsconfig.json

Indicamos como funcionar al compilador de TypeScript

https://goo.gl/74t6CF

{
  "globalDependencies": {
    "core-js": "registry:dt/core-js#0.0.0+20160317120654",
    "jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
    "node": "registry:dt/node#4.0.0+20160509154515"
  }
}

typings.json

Agregamos definiciones que TypeScript no reconoce de forma nativa

Nota: si tienes que integrarte con alguna librería existente, si es popular,  probablemente encuentre los typings en https://github.com/DefinitelyTyped/DefinitelyTyped

{
  "name": "angular2-workshop",
  "version": "1.0.0",
  "scripts": {
    "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
    "lite": "lite-server",
    "postinstall": "typings install",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "typings": "typings"
  },
  "license": "ISC",
  "dependencies": {
    "@angular/common":  "2.0.0-rc.1",
    "@angular/compiler":  "2.0.0-rc.1",
    "@angular/core":  "2.0.0-rc.1",
    "@angular/http":  "2.0.0-rc.1",
    "@angular/platform-browser":  "2.0.0-rc.1",
    "@angular/platform-browser-dynamic":  "2.0.0-rc.1",
    "@angular/router":  "2.0.0-rc.1",
    "@angular/router-deprecated":  "2.0.0-rc.1",
    "@angular/upgrade":  "2.0.0-rc.1",
    "systemjs": "0.19.27",
    "core-js": "^2.4.0",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.6",
    "zone.js": "^0.6.12",
    "angular2-in-memory-web-api": "0.0.10",
    "bootstrap": "^3.3.6"
  },
  "devDependencies": {
    "concurrently": "^2.0.0",
    "lite-server": "^2.2.0",
    "typescript": "^1.8.10",
    "typings":"^1.0.4"
  }
}

package.json

Configuramos el proyecto

/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function(global) {
  // map tells the System loader where to look for things
  var map = {
    'app':                        'app', // 'dist',
    '@angular':                   'node_modules/@angular',
    'rxjs':                       'node_modules/rxjs'
  };
  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main: 'main.js',  defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' }
  };
  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'router-deprecated',
    'upgrade',
  ];
  // Add package entries for angular packages
  ngPackageNames.forEach(function(pkgName) {
    packages['@angular/'+pkgName] = { main: pkgName + '.umd.js', defaultExtension: 'js' };
  });
  var config = {
    map: map,
    packages: packages,
    baseURL: '/'
  };
  System.config(config);
})(this);

systemjs.config.js

Configuramos systemjs

Casi estamos listos para implementar un hola mundo

> npm install

Instalamos las dependencias

Ahora si podemos empezar tirar código 😊

https://goo.gl/74t6CF

2. Hola mundo o si mi entorno ya funciona

https://goo.gl/74t6CF

import {Component} from '@angular/core';

@Component({
    selector: 'my-app',
    template: '<h1>Mi primera app: chispas.✨</h1>'
})
export class AppComponent { }

app/app.component.ts

¡Mi primer componente, chispas!  

https://goo.gl/74t6CF

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

app/main.ts

Donde decimos que hay que arrancar

¿'@angular/platform-browser-dynamic'?
¿por qué no está en '@angular/core'?

Como se arranca una aplicación es específico de la plataforma. Otras opciones pueden ser NativeScript, Apache Cordoba o incluso el servidor.

<html>
  <head>
    <title>Angular 2 Workshop</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>
    <base href="/">
  </head>
  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

index.html

index.html

  • Polyfills de navegadores
  • angular2 polyfills: zones y reflect-metadata para decoradores
  • SystemJS como module loader, aunque podrías usar otro como webpack

https://goo.gl/74t6CF

/* Master Styles */
h1 {
  color: #369; 
  font-family: Arial, Helvetica, sans-serif;   
  font-size: 250%;
}
h2, h3 { 
  color: #444;
  font-family: Arial, Helvetica, sans-serif;   
  font-weight: lighter;
}
body { 
  margin: 2em; 
}
body, input[text], button { 
  color: #888; 
  font-family: Cambria, Georgia; 
}
a {
  cursor: pointer;
  cursor: hand;
}
button {
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button:disabled {
  background-color: #eee;
  color: #aaa; 
  cursor: auto;
}

/* Navigation link styles */
nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}
nav a:visited, a:link {
  color: #607D8B;
}
nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}
nav a.router-link-active {
  color: #039be5;
}

/* items class */
.items {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 24em;
}
.items li {
  cursor: pointer;
  position: relative;
  left: 0;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  height: 1.6em;
  border-radius: 4px;
}
.items li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
}
.items li.selected:hover {
  background-color: #BBD8DC;
  color: white;
}
.items .text {
  position: relative;
  top: -3px;
}
.items {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 24em;
}
.items li {
  cursor: pointer;
  position: relative;
  left: 0;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  height: 1.6em;
  border-radius: 4px;
}
.items li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
}
.items li.selected {
  background-color: #CFD8DC;
  color: white;
}

.items li.selected:hover {
  background-color: #BBD8DC;
}
.items .text {
  position: relative;
  top: -3px;
}
.items .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;
}

/* everywhere else */
* { 
  font-family: Arial, Helvetica, sans-serif; 
}

styles.css

https://goo.gl/74t6CF

> npm start

🍀

https://goo.gl/74t6CF

https://goo.gl/74t6CF

3. Introduciendo el modelo estándar

y los componentes de Angular
 

https://goo.gl/74t6CF

Un poco de interpolación.

import {Component} from '@angular/core';

@Component({
  selector: 'my-app',
  template: '<h1>{{titulo}}</h1>'
})

export class AppComponent {
  titulo = 'Modelo estándar';
}

app/app.component.ts

https://goo.gl/74t6CF

//...

export class Particle {
  id: string;
  name: string;
  mass: string;
  charge: string;
  spin: string;
  type: ParticleType;
  forces: Force[];
}

type ParticleType  = 'Quark' | 'Lepton' | 'Boson';

type Force  = 'Strong' | 'Electromagnetic' | 'Weak';

app/app.component.ts

https://goo.gl/74t6CF

Agreguemos una clase


  particle: Particle = {
      id: 'u',
      name: 'up',
      mass: '2.3MeV/c²',
      charge: '2/3',
      spin: '1/2',
      type: 'Quark',
      forces: ['Electromagnetic', 'Strong', 'Weak']
  };

app/app.component.ts

Agregamos la propiedad a AppComponent

template: '<h1>{{titulo}}</h1><p>Los detalles del {{particle.type}} <b>{{particle.name}}.</b></p>'

Y cambiamos el template

https://goo.gl/74t6CF


 template: `
  <h1>{{titulo}}</h1>
  <h2>Detalles del {{particle.type}} {{particle.name}}.</h2>
  <p>Masa: {{particle.mass}}</p>
  <p>Carga: {{particle.charge}}</p>
  <p>Spin: {{particle.spin}}</p>
  <p>Type: {{particle.type}}</p>
  <p>Fuerzas: {{particle.forces.join(', ')}}</p>
 `

app/app.component.ts

El backtick ` nos permite un template más manejable

https://goo.gl/74t6CF


 template: `
  <h1>{{titulo}}</h1>
  <h2>Detalles del {{particle.type}} {{particle.name}}.</h2>
  <p>Masa: {{particle.mass}}</p>
  <p>Carga: {{particle.charge}}</p>
  <p>Spin: {{particle.spin}}</p>
  <p>Type: {{particle.type}}</p>
  <p>Fuerzas: {{particle.forces.join(', ')}}</p>
   <div>
    <label>Ajusta la masa: </label>
    <input value="{{particle.mass}}" placeholder="masa">
  </div>
 `

app/app.component.ts

Ajustando la masa

https://goo.gl/74t6CF


 template: `
  <h1>{{titulo}}</h1>
  <h2>Detalles del {{particle.type}} {{particle.name}}.</h2>
  <p>Masa: {{particle.mass}}</p>
  <p>Carga: {{particle.charge}}</p>
  <p>Spin: {{particle.spin}}</p>
  <p>Type: {{particle.type}}</p>
  <p>Fuerzas: {{particle.forces.join(', ')}}</p>
   <div>
    <label>Ajusta la masa: </label>
    <input [(ngModel)]="particle.mass"  placeholder="masa">
  </div>
 `

app/app.component.ts

Double binding

https://goo.gl/74t6CF

4. El listado de partículas fundamentales 

y un master-detail con Angular
 

https://goo.gl/74t6CF

//...

var PARTICLES: Particle[] = [
    { id: 'u', name: 'up', mass: '2.3MeV/c²', charge: '2/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
    { id: 'd', name: 'down', mass: '4.8MeV/c²', charge: '1/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
    { id: 'c', name: 'charm', mass: '1.275GeV/c²', charge: '2/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
    { id: 's', name: 'strange', mass: '95MeV/c²', charge: '1/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
    { id: 't', name: 'top', mass: '173.07GeV/c²', charge: '2/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
    { id: 'b', name: 'bottom', mass: '4.18GeV/c²', charge: '1/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
    { id: 'e', name: 'electron', mass: '0.511MeV/c²', charge: '-1', spin: '1/2', type: 'Lepton', forces: ['Electromagnetic', 'Weak'] },
    { id: 'μ', name: 'muon', mass: '105.7MeV/c²', charge: '-1', spin: '1/2', type: 'Lepton', forces: ['Electromagnetic', 'Weak'] },
    { id: 'τ', name: 'tau', mass: '1.777GeV/c²', charge: '-1', spin: '1/2', type: 'Lepton', forces: ['Electromagnetic', 'Weak'] },
    { id: 'νe', name: 'electron neutrino', mass: '<2.2eV/c²', charge: '0', spin: '1/2', type: 'Lepton', forces: ['Weak'] },
    { id: 'νμ', name: 'muon neutrino', mass: '<0.17MeV/c²', charge: '0', spin: '1/2', type: 'Lepton', forces: ['Weak'] },
    { id: 'ντ', name: 'tau neutrino', mass: '<15.5MeV/c²', charge: '0', spin: '1/2', type: 'Lepton', forces: ['Weak'] },
    { id: 'g', name: 'gluon', mass: '0', charge: '0', spin: '1', type: 'Boson', forces: ['Strong'] },
    { id: 'γ', name: 'photon', mass: '0', charge: '0', spin: '1', type: 'Boson', forces: ['Electromagnetic'] },
    { id: 'Z', name: 'Z boson', mass: '0', charge: '0', spin: '1', type: 'Boson', forces: ['Weak'] },
    { id: 'W', name: 'W boson', mass: '0', charge: '0', spin: '±1', type: 'Boson', forces: ['Weak'] },
    { id: 'H', name: 'Higgs boson', mass: '0', charge: '0', spin: '0', type: 'Boson', forces: [] }
];

app/app.component.ts

El listado de partículas

app/app.component.ts

export class AppComponent {
  titulo = 'Modelo estándar';
  public particles = PARTICLES;
}

Agregamos las partículas al componente

https://goo.gl/74t6CF

app/app.component.ts

 <ul class="particles">
    <li *ngFor="let particle of particles">
        <span class="code">{{particle.id}}</span>
        {{particle.name}}
    </li>
  </ul>

Y agregamos al template:

Si la página deja de funcionar, mira la consola. Lo que referencia a la propiedad 'particle' de AppComponent está fallando con un null pointer (por ejemplo, "Cannot read property 'type' of undefined"). 



https://goo.gl/74t6CF

app/app.component.ts

  <div *ngIf="particle">
      <h2>Detalles del {{particle.type}} {{particle.name}}.</h2>
      <p>Masa: {{particle.mass}}</p>
      <p>Carga: {{particle.charge}}</p>
      <p>Spin: {{particle.spin}}</p>
      <p>Type: {{particle.type}}</p>
      <p>Fuerzas: {{particle.forces.join(', ')}}</p>
      <div>
        <label>Ajusta la masa: </label>
        <input [(ngModel)]="particle.mass" placeholder="masa">
      </div>
  </div>

Evitemos el null pointer.

https://goo.gl/74t6CF

app/app.component.ts

 styles:[`
  .selected {
    background-color: #CFD8DC !important;
    color: white;
  }
  .particles {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
    float: left;
  }
  .particles li {
    cursor: pointer;
    position: relative;
    left: 0;
    background-color: #EEE;
    margin: .5em;
    padding: .3em 0;
    height: 1.6em;
    border-radius: 20px 4px 4px 20px;
  }
  .particles li.selected:hover {
    background-color: #BBD8DC !important;
    color: white;
  }
  .particles li:hover {
    color: #607D8B;
    background-color: #DDD;
    left: .1em;
  }
  .particles .text {
    position: relative;
    top: -3px;
  }
  .particles .code {
    display: inline-block;
    text-align: center;
    font-size: small;
    color: white;
    width: 1.2em;
    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: 20px;
  }
`]

Agreguemos unos estilos en @Component

app/app.component.ts

<li *ngFor="let particle of particles" (click)="onSelect(particle)">

Bind al evento click del li 

La sintaxis (evento)="expresion", agrega un listener sobre evento, y evalúa expresion cuando se produce.

https://goo.gl/74t6CF

app/app.component.ts

selectedParticle: Particle;

Guardaremos la partícula seleccionada

onSelect(particle: Particle) { this.selectedParticle = particle; }

y definimos onSelect

https://goo.gl/74t6CF

app/app.component.ts

 <div *ngIf="selectedParticle">
      <h2>Detalles del {{selectedParticle.type}} {{selectedParticle.name}}.</h2>
      <p>Masa: {{selectedParticle.mass}}</p>
      <p>Carga: {{selectedParticle.charge}}</p>
      <p>Spin: {{selectedParticle.spin}}</p>
      <p>Type: {{selectedParticle.type}}</p>
      <p>Fuerzas: {{selectedParticle.forces.join(', ')}}</p>
      <div>
        <label>Ajusta la masa: </label>
        <input [(ngModel)]="selectedParticle.mass" placeholder="masa">
      </div>
  </div>

En la plantilla, pintamos la partícula seleccionada con

https://goo.gl/74t6CF

app/app.component.ts

[class.selected]="particle === selectedParticle"

Podemos marcar el elemento li seleccionado con

https://goo.gl/74t6CF

4. Partículas compuestas 

o creando componentes con Angular
 

https://goo.gl/74t6CF

El código está quedando farragoso, por lo que el siguiente paso es cortarlo en partes más manejables, mantenibles y fáciles de entender.

https://goo.gl/74t6CF

app/particle-detail.component.ts

import {Component} from '@angular/core';

@Component({
  selector: 'my-particle-detail',
})
export class ParticleDetailComponent {
}

Nuevo componente en fichero nuevo.

https://goo.gl/74t6CF

app/particle-detail.component.ts

template:` <div *ngIf="particle">
       <h2>Detalles del {{particle.type}} {{particle.name}}.</h2>
       <p>Masa: {{particle.mass}}</p>
       <p>Carga: {{particle.charge}}</p>
       <p>Spin: {{particle.spin}}</p>
       <p>Type: {{particle.type}}</p>
       <p>Fuerzas: {{particle.forces.join(', ')}}</p>
       <div>
         <label>Ajusta la masa: </label>
         <input [(ngModel)]="particle.mass" placeholder="masa">
       </div>
   </div>
  `

Nos traemos su fragmento de template (cambiando selectedParticle por particle).

https://goo.gl/74t6CF

app/particle-detail.component.ts

particle: Particle;

Agregamos la propiedad 

https://goo.gl/74t6CF

app/particle.ts

export class Particle {
  id: string;
  name: string;
  mass: string;
  charge: string;
  spin: string;
  type: ParticleType;
  forces: Force[];
}

export type ParticleType  = 'Quark' | 'Lepton' | 'Boson';

export type Force  = 'Strong' | 'Electromagnetic' | 'Weak';

Definimos la partícula de forma centralizada

https://goo.gl/74t6CF

app/app.component.ts

import {Particle} from './particle';

y agregamos los correspondientes imports

app/particle-detail.component.ts

https://goo.gl/74t6CF

app/app.component.ts

<my-particle-detail [particle]="selectedParticle"></my-particle-detail>

Usamos detail "my-particle-detail" en la plantilla:

https://goo.gl/74t6CF

app/particle-detail.component.ts

@Input() 
particle: Particle;

Anotamos una propiedad del componente para que pueda recibir datos.
(*cuidado con dejarse los paréntesis en la anotación)

import {Component, Input} from '@angular/core';

y hay que cambiar el import

https://goo.gl/74t6CF

app/app.component.ts

import {ParticleDetailComponent} from './particle-detail.component';

Importamos el componente particle-detail para poder usarlo

https://goo.gl/74t6CF

directives: [ParticleDetailComponent]

Y le informamos a @Component de que tiene que usarlo al interpretar la plantilla

4. Fabricando partículas 

desde servicios de Angular
 

https://goo.gl/74t6CF

app/particle.service.ts

import {Injectable} from '@angular/core';

@Injectable()
export class ParticleService {
}

Creamos un servicio que devuelva la información necesaria.

https://goo.gl/74t6CF

app/particle.service.ts

export class ParticleService {
    getParticles(){
        return PARTICLES;
    }
}

Le dotamos de contenido:

import {PARTICLES} from './mock-particles';
import {Particle} from './particle';

export var PARTICLES: Particle[] = [
    { id: 'u', name: 'up', mass: '2.3MeV/c²', charge: '2/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
    { id: 'd', name: 'down', mass: '4.8MeV/c²', charge: '1/3', spin: '1/2', type: 'Quark', forces: ['Electromagnetic', 'Strong', 'Weak'] },
//...    

app/mock-particles.ts

app/app.component.ts

Importamos el servicio

import {ParticleService} from './particle.service';
constructor(private _particleService: ParticleService) { }

Agregamos la dependecias

En @Component agregamos el provedor para indicar al sistema de DI de donde sacar el servicio 

providers: [ParticleService]

https://goo.gl/74t6CF

El componente tiene un ciclo de vida: ngOnInit, ngOnChanges, ngDoCheck, ngOnDestroy

En ngOnInit inicializamos la propiedad particles

app/app.component.ts


export class AppComponent {
  ngOnInit() {
    this.getParticles();
  }
  getParticles() {
    this.particles = this._particleService.getParticles();
  }
}

https://goo.gl/74t6CF

El servicio debería ser asíncrono

app/particle.service.ts

    getParticles() {
      return Promise.resolve(PARTICLES);
    }
    getParticlesSlowly() {
      return new Promise<Particle[]>(resolve =>
          setTimeout(()=>resolve(PARTICLES), 2000) 
      );
    }

https://goo.gl/74t6CF

Al resolver la promesa guardamos las partículas recibidas

app/app.component.ts

  getParticles(){
      this._particleService.getParticles().then(
          particles => this.particles = particles
      )
  }

https://goo.gl/74t6CF

5. Navegando por la realidad

y las rutas con Angular
 

https://goo.gl/74t6CF

Vamos a agregar tres estados a la aplicación:

  • Vista por familias
  • Vista de listado
  • Vista de detalle

https://goo.gl/74t6CF

app/app.component.ts

app/particles.component.ts

renombramos

como

cambiamos el nombre de la clase  ParticlesComponent y el selector a my-particles

https://goo.gl/74t6CF

app/app.component.ts

como

import { Component }       from '@angular/core';
import { ParticleService }     from './particle.service';
import { ParticlesComponent } from './particles.component';
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <my-particles></my-particles>
  `,
  directives: [ParticlesComponent],
  providers: [
    ParticleService
  ]
})
export class AppComponent {
  titulo = 'Modelo estándar';
}

Elimina el provider de particles.component.ts

index.html

agregamos la referencia al módulo de routing

<base href="/">

https://goo.gl/74t6CF

app/app.component.ts

Agregamos una navegación en app component

import { Component }       from '@angular/core';
import { ParticleService }     from './particle.service';
import { ParticlesComponent } from './particles.component';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS }
       from '@angular/router-deprecated';
@RouteConfig([
  {
    path: '/particles',
    name: 'Particles',
    component: ParticlesComponent
  }
])

Configuramos la ruta

app/app.component.ts

Agregamos un enlace para navegar y un lugar donde pintar el contenido

 template: `
    <h1>{{title}}</h1>
    <a [routerLink]="['Particles']">Particles</a>
    <a [routerLink]="['Families']">Families</a>
    <router-outlet></router-outlet>
  `,

app/app.component.ts

Configuramos la inyección de dependencias de la aplicación

  directives: [ROUTER_DIRECTIVES],
  providers: [
    ROUTER_PROVIDERS,
    ParticleService
  ]

app/particles.component.ts

Al hacer click navegaremos al detalle como nuevo estado

<h1>{{titulo}}</h1>
<ul class="particles">
  <li *ngFor="let particle of particles"
    (click)="onSelect(particle)"
    [class.selected]="particle === selectedParticle">
      <span class="code">{{particle.id}}</span>
      {{particle.name}}
  </li>
</ul>
<div *ngIf="selectedParticle">
    <p>Selected particle: {{selectedParticle.type}} {{selectedParticle.name}}</p>
    <button (click)="gotoDetail()">Ir al detalle</button>
</div>
templateUrl: 'app/particles.component.html',
styleUrls: ['app/particles.component.css']

app/particles.component.html

app/particles.component.css

Metemos lo que había en la propiedad styles

app/app.component.ts

Y agregamos una nueva anotación a la clase para configurar las rutas

@RouteConfig([
  {path:'/families',  name: 'Families',  component: FamiliesComponent},
  {path:'/particles', name: 'Particles', component: ParticlesComponent, useAsDefault: true}
])
export class AppComponent {
import {Component} from '@angular/core';

@Component({
    template: 'Families'
})
export class FamiliesComponent {

}

lo dejamos de momento muy básico (para desarrollar en otro momento)

app/families.component.ts

Para navegar a Detail hay que registrar la ruta y llamar al router

  constructor(
       private _router: Router,
       private _particleService: ParticleService
  ) {}

app/app.component.ts

app/particles.component.ts

{path: '/detail/:id', name: 'ParticleDetail', component: ParticleDetailComponent }
  gotoDetail(){
      this._router.navigate(['ParticleDetail', { id: this.selectedParticle.id }]);
  }
import {Router} from '@angular/router-deprecated';

Detail debe sacar el id de la partícula de la ruta

app/particle-detail.component.ts

import {Component} from '@angular/core';
import {Particle} from './particle';
import {ParticleService} from './particle.service'
import {RouteParams} from 'angular2/router';
   constructor(
      private _particleService: ParticleService,
      private _routeParams: RouteParams) {
    }

Pedimos los dos servicios necesarios

Detail debe sacar el id de la partícula de la ruta

app/particle-detail.component.ts

   ngOnInit() {
      let id = this._routeParams.get('id');
      this._particleService.getParticle(id)
        .then(particle => this.particle = particle);
    }

Agregamos el método que falta

app/particle.service.ts

    getParticle(id) {
        return this.getParticles().then(
            particles => particles.filter(
                particle => particle.id === id
            )[0]
        )
    }

https://goo.gl/74t6CF

Volver atrás

app/particle.component.ts

    goBack() {
      window.history.back();
    }

https://goo.gl/74t6CF

6. Teletransportación

y las peticiones $http con Angular
 

https://goo.gl/74t6CF

mock/particles.json

[
    { "id": "u", "name": "up", "mass": "2.3MeV/c²", "charge": "2/3", "spin": "1/2", "type": "Quark", "forces": ["Electromagnetic", "Strong", "Weak"] },
    { "id": "d", "name": "down", "mass": "4.8MeV/c²", "charge": "1/3", "spin": "1/2", "type": "Quark", "forces": ["Electromagnetic", "Strong", "Weak"] },
    { "id": "c", "name": "charm", "mass": "1.275GeV/c²", "charge": "2/3", "spin": "1/2", "type": "Quark", "forces": ["Electromagnetic", "Strong", "Weak"] },
    { "id": "s", "name": "strange", "mass": "95MeV/c²", "charge": "1/3", "spin": "1/2", "type": "Quark", "forces": ["Electromagnetic", "Strong", "Weak"] },
    { "id": "t", "name": "top", "mass": "173.07GeV/c²", "charge": "2/3", "spin": "1/2", "type": "Quark", "forces": ["Electromagnetic", "Strong", "Weak"] },
    { "id": "b", "name": "bottom", "mass": "4.18GeV/c²", "charge": "1/3", "spin": "1/2", "type": "Quark", "forces": ["Electromagnetic", "Strong", "Weak"] },
    { "id": "e", "name": "electron", "mass": "0.511MeV/c²", "charge": "-1", "spin": "1/2", "type": "Lepton", "forces": ["Electromagnetic", "Weak"] },
    { "id": "μ", "name": "muon", "mass": "105.7MeV/c²", "charge": "-1", "spin": "1/2", "type": "Lepton", "forces": ["Electromagnetic", "Weak"] },
    { "id": "τ", "name": "tau", "mass": "1.777GeV/c²", "charge": "-1", "spin": "1/2", "type": "Lepton", "forces": ["Electromagnetic", "Weak"] },
    { "id": "νe", "name": "electron neutrino", "mass": "<2.2eV/c²", "charge": "0", "spin": "1/2", "type": "Lepton", "forces": ["Weak"] },
    { "id": "νμ", "name": "muon neutrino", "mass": "<0.17MeV/c²", "charge": "0", "spin": "1/2", "type": "Lepton", "forces": ["Weak"] },
    { "id": "ντ", "name": "tau neutrino", "mass": "<15.5MeV/c²", "charge": "0", "spin": "1/2", "type": "Lepton", "forces": ["Weak"] },
    { "id": "g", "name": "gluon", "mass": "0", "charge": "0", "spin": "1", "type": "Boson", "forces": ["Strong"] },
    { "id": "γ", "name": "photon", "mass": "0", "charge": "0", "spin": "1", "type": "Boson", "forces": ["Electromagnetic"] },
    { "id": "Z", "name": "Z boson", "mass": "0", "charge": "0", "spin": "1", "type": "Boson", "forces": ["Weak"] },
    { "id": "W", "name": "W boson", "mass": "0", "charge": "0", "spin": "±1", "type": "Boson", "forces": ["Weak"] },
    { "id": "H", "name": "Higgs boson", "mass": "0", "charge": "0", "spin": "0", "type": "Boson", "forces": [] }
]

https://goo.gl/74t6CF

app/main.ts

import { bootstrap }      from '@angular/platform-browser-dynamic';
import { HTTP_PROVIDERS } from '@angular/http';

import { AppComponent }   from './app.component';

bootstrap(AppComponent, [ HTTP_PROVIDERS ]);

https://goo.gl/74t6CF

app/hero.service.ts

Completamos el servicio con la llamada http

import { Injectable }    from '@angular/core';
import { Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

La importación de 'toPromise' agrega automáticamente el método a Observable

app/hero.service.ts

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
               .toPromise()
               .then(response => response.json().data)
               .catch(this.handleError);
  }

  private handleError(error: any) {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }

Completamos el servicio con la llamada http

private particlesUrl = 'app/particles.json';

constructor(private http: Http) { }

El servicio http devuelve un observable y no una promesa

https://goo.gl/74t6CF

Algunos recursos

¡Gracias!

 

Gonzalo Ruiz de Villa

AngularJS Madrid

JSDayES 2016

 

https://goo.gl/74t6CF

Angular 2 workshop

By Gonzalo Ruiz de Villa

Angular 2 workshop

Slides para el workshop de Angular2

  • 14,555
Loading comments...

More from Gonzalo Ruiz de Villa