martes, 20 de diciembre de 2016

Angular 2. Formularios. Entrega 1

Voy a seguir esta vez la guia oficial de Angular 2.


1. Creación del proyecto con angular-cli.

Vamos a crear un proyecto llamado angular-forms. Para ello lo haremos tal como dijimos en un post anterior. Hay que tener en cuenta que en entornos windows no hay que indicar el "sudo", pero hay que ejecutar el comando en interprete de comandos en modo administrador.

sudo ng new angular-forms

(lo de sudo es si utilizamos Ubuntu) y esperamos lo nuestro.

y nos situamos en la carpeta angular-forms y ejecutamos

sudo ng serve 

para ver si funciona (abrimos el navegador en http://localhost:4200 y debe aparecer el texto "app works!" en el navegador).


2. Construcción de un formulario con un componente y template (plantilla).

Hay que tener cuidadado si utilizamos angular.cli para hacer este ejemplo ya que:

1. A veces no refresca el contenido bien o lo bastante rápido. A veces parece que se ha refrescado pero, no ha compilado bien. En este caso, sucede que no reconoce la función newHero(). Tras meterle dentro codigo adicional como :
  "console.log("lo que sea"); 
ha funcionado correctamente.


2.Las rutas que se proponen en el ejemplo, deben de ser relativas al elemento que se indica (tienen que ser del tipo "./". Por ejemplo si copiamos el componente hero-form.component.ts del ejemplo, observamos esta línea:
   templateUrl:'hero-form.component.html'
Pues, debería ser esta otra            
  templateUrl:'./hero-form.component.html'

3. La distribución de los componentes puede no ser la misma. Por ejemplo el fichero index.html, angular cli lo mete en la carpeta padre de la carpeta app, y no en la misma como se muestra en el tutorial de angular.

4. Para acceder al CSS que nos proporciona Bootstrap (no confundir con el comando Bootstrap que aparece dentro de los componentes), se puede meter en la sección "head" del index.html


<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

5. El fichero de estilos forms.css debera estar en el mismo directorio que index.html o sea en src, pero para acceder a él será tambien con ruta relativa "./" y no en forma absoluta, si no no lo encuentra.

<link rel="stylesheet" href="./forms.css">

Dicho esto, ya funciona el ejemplo de la documentación de angular.

Vamos a destacar lo más importante:

1. Se crea un clase Hero que se guarda en hero.ts (dentro de la carpeta app), que define la información a guardar.
-----------------------------------------------------------------------------------------------------------------


2. Se crea el componente HeroFormComponent que se guarda en hero-form.component.ts (dentro de la carpeta app), que:

  2.1 hay que indicarle la ruta RELATIVA al componente html:
       templateUrl:'./hero-form.component.html'

2.2 No admite la sentencia:
       moduleId: module.id,

2.3 Se definen estas funciones, que seran llamadas en los botones del form:
        onSubmit() { this.submitted = true; }
newHero() {this.model = new Hero(42, '', ''); }
 
  2.4 Inicialmente se definió el "getter" diagnostic para tener indicadores.
        get diagnostic() { return JSON.stringify(this.model); }
  2.5 Se tiene que importar la clase Hero
        import { Hero } from './hero'

-----------------------------------------------------------------------------------------------------------------

3. Se crea la plantilla hero.form.components.html que es capaz de recoger información del componente:
  3.1 Para mostrar información fuera de una etiqueta html se utiliza la notación moustache:
        {{diagnostic}}
3.2 Dentro del la etiqueta html hay que utilizar [ngModel] para vincular a un atributo del componente.
  3.3 Tambiém se pueden definir eventos dentro de la etiquete entre paréntesis (ngModelChange) .Para el caso de darle a un botón se le puede asignar varias funciones (Ojo, si hacemos reset al form, se borrará toda la información del form. Por tanto yo invertiría el orden de las funciones a heroForm.reset(); newHero() en vez de lo que se pone a continuación que es copia del tutorial de angular.
<button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>
  3.4 Existen clases que indican que:
       3.4.1: Control visitado o no  (ng-touched, ng-untouched)
       3.4.2: Control con valor cambiado o no (ng-dirty, ng-pristine)
       3.4.3: Contor con valor correcto o ni (ng-valid, ng-invalid)
   3.5 Para hacer referencia a un componente se define dentro de el com #nombre_componente y después de puede acceder a el con notación mustache. En este caso se le ha dado el nombre spy
<input type="text" class="form-control" id="name"
  required
  [(ngModel)]="model.name" name="name"
  #spy >
<br>TODO: remove this: {{spy.className}}
    y devuelve "TODO: remove this: form-control ng-untouched ng-pristine" que contine la información de las clases css del control texto.

  3.6 Se les ha dado el atributo required para la validación en algunos campos.
  3.7 Como ya hemos dicho en el punto 3.3, la función reset() del formulario borra el contenido de los controles, por tanto si ejecutamos newHero() despues del reset, los controles se llenaran con la información proporcionada por defecto en este constructor. Si se ejecuta anteriormente, se perderán los valores por omisión, y el formulario se quedará en blanco.
  3.8 Se pueden asociar atributos de las tags de html a variables del componente. Por ejemplo el atributo hidden de la etiqueta div, que toma el valor de la variable submitted
<div  [hidden]="submitted"> 
 3.9 La variable o propiedad valid del form se puede asignar también al atributo disabled de un control:
<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button>

3.10 Con *ngFor rellenamos el control select
  <select class="form-control" id="power" required>
      <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
  </select>


-----------------------------------------------------------------------------------------------------------------

4. El módulo app.module.ts debe de hacer referncias a HeroComponent tanto en import como en declarations (y como estamos utilizando forms, también debe importar FormsModule)
    ......
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroFormComponent } from './hero-form.component';

 @NgModule({
....
declarations: [ AppComponent, HeroFormComponent ],
....
})


Al final queda


viernes, 16 de diciembre de 2016

¿Que co?? es Jenkins?

No tengo "npi" (ni puñetera idea) que es Jenkins. Así que agradezco la aportación de Ana M. del Carmen García Oterino, para ver si también me entero yo.

1. ¿Que es Jenkins?

  • Es un servidor de integración continua. (Y ¿?)
  • Es open source, y gratuito.
  • Es un "fork" de Hudson.
  • La base de Jenkins son las tareas.

2. ¿Que es la integració contínua?

  • Es una práctica de desarrollo de software donde los miembros del equipo conjuntan o integran su trabajo.
  • Como mínimo es una práctica diaria.
  • En cada integración o conjuntación se hace un build de forma automatizada.
  • En cada integración se oasan las pruebas y métricas de calidad para detectar errores y deficiencias lo antes posible.

3. ¿Que se consigue con la integración contínua?

  • Se puede saber el estado del software en cada momento.(sabedr que funciona y lo que no)
  • Monitorizar la calidad del código y su cobertura de pruebas.
  • Reducción de la deuda técnica, manteniendo costes bajos.

4. ¿Que permite hacer Jenkins?

  • Se puede programar una tarea para que compruebe el repositorio de control de versiones cada cierto tiempo y que cuando un desarrollador quiera subir su código al control de versiones, este código se compile y se ejecuten las pruebas.
  • Notificar al desarrollador o equipo de calidad si las pruebas no han sido satisfactorias.
  • Si el build es correcto, se puede indicar a Jenkins que intente integrar el código y subirlo al repositorio de control de versiones.
  • Permite integrar el código periódicamente.
  • Actua como herramienta de enlace en todo el proceso de desarrollo.
  • Lanzar métricas de calidad y evaluar los resultados.
  • Generar documentación del proyecto.
  • Pasar una version estable del código al departamento de calidad, a pre-producción o produccion

5. Prerequisos para utilizar Jenkins

  • Tener un repositorio de control de versiones (mercurial, git, svn, plastic,etc.)
  • Guardar en dicho repositorio las herramientas necesarias para hacer el build (código, scripts de test, librerías adjuntas,..)
  • El equipo debe estar mentalizado y comprmetido.

6. Disciplina

  • Todo el código debe subirse periódicamente al repositorio del control de versiones, por parte de los desarrolladores.
  • Los proyectos deben de tener un proceso de build automático.ç
  • Si un build falla, es prioridad absoluta arreglarlo

7. ¿Que puede pasar si no se utiliza Jenkins?

  • Se pueden multiplicar el número de personas que trabajan en un desarrollo de software.
  • Se puede perder el control del software (se encarece)
  • Se pueden perder las fuentes.
  • Reaparición de bugs que estaban reparados.
  • Trabajadores que no pueden programar mientras se los que "testers" prueban..
  • Disparar los costes de mantenimiento.
  • Aparición de múltiples versiones en producción, una para cada cliente.

8. Instalar Jenkins

Vamos a seguir el tutorial de Tutorials Point.
  • Vamos a la página de Jenkins y le damos al boton dowload (descargamos un war para ser desplegado en un Tomcat) También se puede ejecutar como standalone con "java -jar Jenkins.war ". Se puede acceder a él , por navegador con http://localhost:8080
  • Descargamos también Apache Maven para gestionar las dependencias
  • Descargamos tambien Git.
  • Primero instalamos it, después Maven, y por último Jenkins.
  • Hay que crear dar al PATH la ruta de Maven para poder ejecutarlo
  • Ejecutamos Jenkins, damos administrar, plugins, instalar e instalamos el plugin de GitHub.
  • Ahora vamos a Administrar Jenkins, configurar el sistema, donde configuraremos las rutas y variables para acceder al (1) JDK, (2) Git, (3) Maven, para indicar las rutas donde están instalados.

9. Crear tareas en Jenkins

  • Damos a crear tareas, le damos un nombre, y podemos decirle que es de tipo proyecto maven, pero le tendemos que dar la URL del repositorio Git, junto con las credenciales para acceder a él . Luego marcamos la casilla de "Build when a change is pushed to GitHub", y le indicamos la ruta al fichero pom.xml del repositorio. Le damos a Guardar
  • Una vez guardado, podemos decirle que "ejecute un comando posterior", y le indicamos el comando (shell), por ejemplo que copie el war generado del repositorio, a la carpeta de despliegue del servidor de aplicaciones.
  • Ahora le decimos "construir ahora" que es hacer un build y verificamos si copia el war a nuestro servidor.
  • Hay un historial de tareas, que dando un click sobre nuestra tarea aparece mas información.
  • Hay una opción en dicha tarea que es mostrar una consola para ver como se ejecuta las tarea.








Angular 2. Entrega 16: Conclusiones

Para finalizar, dar las gracias por esta magnífica introducción a Angular 2 de Micael Gallego, que hemos intentado resumir.

Conclusiones. Cosas que hemos visto.


  • Es para desarrollar aplicaciones SPA (Single Page Applications)
  • Se recomienda utilizarlo con TypeScript
  • Orientado a componentes, con inyección de dependencias y templates (plantillas)
  • No es compatible con Angular 1
  • Micael Gallego le ve futuro ya que está de la mano de Google.

Cosas que no se han visto

  • Validación de formularios (con NgForm y NgControl). Formularios dirty, campos obligatorios,..
  • Testing unitario (de componentes o servicios) y de integración (Jasmine, Karma, Protactor, Inyecotr de dependencias de testing..) o e2e, que son los que simulan a un usuario en el navegador. Recordar que un test unitario comprueba en principio un método de un componente, y en caso de hacer referencia a otro componente o servicio, se debera sustituir por un mock. Un test de integración, comprueba el funcionamiento conjunto de los componentes como un equipo. Y el e2e, intenta ejecutar nuestro programa en un navegador.
  • Carga bajo demanda de componentes (para acelerar la carga inicial de la aplicación  )
  • Gestión al estilo Redux (ngrx).
  • Animaciones.
  • Se pueden hacer aplicaciones con múltipes @NgModule (para estructurar componentes y dependencias), a utilizar cuando la aplicación crece mucho. Es conveniente agrupar los componentes que estan banstante relacionados en un mismo módulo. Al final no se reutilizan componentes sino un módulo completo.
  • Optimización de app en producción: (1) Compilador de templates a TS (angular-copmpiler), (2) eliminación de funcionalidades de la libreria no usadas ..(tree shaking). Pero todavía no está bien incluido en angular.
  • Angular Universal: Nueva tecnologia de renderizado (generación código final html) en el servidor para optimizar la descarga inicial. Se envia el código html al cliente y el javascript se va descargando en 2º plano.

Ecosistema Angular

  • Angular2-electron: Aplicacones de escritorio multiplataforma Angular2
  • Ionic2: Aplicaciones móvil hibricas con Angular2
  • NativeScript: Aplicaciones móviles con UI nativo con Angular2
  • Angular2-Meteor: Framework Javascript/Typescript fullstack para desarrollo de apps web interactivas (comunicación websockets cliente servidor). Es una aplicación de comunicación entre el Front y el Back que no es API REST.
  • AngularFire2: Cliente Angular 2 para el backend as a service de Firebase de Google, para guardar cierta información (uso generalmente para móviles)

Angular 2. Entrega 15: Libreria de componentes

Angular 2 solo admite componentes (1) html o (2) css. No admite librerias javascript como jQuery, javascript de Bootstrap, .. que no esten preparadas para angular2.

Se puede usar las que solo tienen CSS como:



Para ello debemos buscar componentes preparados para angular2. Veamos algunos:

  • ng2-bootstrap : Valor Soft ha migrado el código jQuery de Bootstrap a angular2
  • angular2 Material : Libreria de componentes del equipo de Angular2 en fase beta pero bastante adelantado
  • ag-grid : Tabla con componentes avanzados que tiene muy buena pinta. Es de pago si lo utilizas en software de pago, sino es software libre.
  • SB Admin 2.0 ng2: Template and Dashboard con una demo. Lo mas molon es de pago
  • primeNG : de primafaces




Angular 2. Entrega 14. Aplicaciones multipágina: Router

Seguimos con las magníficas explicaciones de nuestro apreciado amigo Micael Gallego.

"Las webs SPA (single page application) pueden tener varias pantallas simulando la navegación por diferentes páginas".

Imaginemos una página con un índice que accede a otra con el contenido.

1. La página (componente) principal

El componente principal (app-root) tiene:

  1. Una parte fija (cabecera, footer)
  2. Una parte variable cuyo contenido depende  de la URL (<router-outlet>)

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


@Component ({
  selector: 'app-root',
  template: `
    <h1 class="title">Library</h1>
    <router-outlet></router-outlet> <!-- Zona que cambia con la URL--> 
    ´
})
export class AppComponent {}


2. Configuración de las rutas en app.routing.ts 

Hay un componente app-routing.ts donde se definen que componentes se muestran para cada URL

Hay que destacar del código que mostraremos:

1. Las 3 formas distintas de enrutar que hay en appRoutes, donde se indica para cada ruta que componente cargamos. La ruta puede ser fijas( 'books') o con un "placeholder" que se indica con 2 puntos ('books/:id'). También se puede "redireccionar a otra ruta" la cual hemos definido antes con el componente asociado. Por cierto, en este último caso hay que indicar pathMatch:'full'

2. El método de clase RouteModule.forRoot(appRoutes)que mete las rutas en el ngModule. Pero el estilo de meterlas es exportándolas con dicha clase

import {Routes, RouterModule } from '@angular/router';
import {BookListComponent } from './book-list.component';
import {BookDetailComponent } from './book-detail.component';

const appRoutes = [
  { path: 'books/:id', component: BookDetailComponent },
  { path: 'books', component: BookDetailComponent },
  { path: '', redirectTo: 'books', pathMatch:'full' }
]
export const routing = RouteModule.forRoot(appRoutes);

Estas rutas son globales a toda la aplicación, pero hay mucha tela ya que se pueden hacer rutas por componente, carga perezosa (lazy loading)

3. Definición en el app.module.ts 

Hay que importar el routing exportado del app.routing.ts, tanto en la parte import como en imports. Si nos fijamos bien lo estamos dando de alta como un módulo.

...
import {routing} from './app,routing';

@NgModule ({
  declarations: [AppComponent, BookDetailComponent, BookListComponent],
  imports: [BrowserModule, FormsModule, HttpModule, JsonModule, routing],
})
export class AppModule {}


4. ¿Como navegar de un sitio a otro?

Se utilizan  links especiales(evitando href) para navegar dentro de la aplicación web (código html) ([routerLink]) (que es un array de strings), que evitan la recarga global.

Este es book-list.component,ts o sea la LISTA de libros. Cuando hacemos click sobre los elementos comprendidos dentro del routerLink. El routerLink puede incluir la ruta y los parámetros, en ese caso el parámetro es book.id
...
@Component({
  template: `
    <h2>BOOKS</h2>
    <ul>
      <li *ngFor="let book of books">
        <!-- La ruta se puede indicar como un string o un array de estrings si hay parametros -->
        <a [routerLink]="['/book',book.id]">
          {{book.id}}-{{book.title}} <!-- tiene el identificador y titulo del libro -->
        </a>
      <li>
    </ul>
  `
})
export class BookListComponent {
  books: Book[];
  constructor (service: BookService) {
    this.books = service.getBooks();
  }
}

5. Acceder al libro concreto a visualizar en el componente detail

Para ello, tenemos que acceder a los servicios de Router y ActivatedRoute, y por supuesto a los servicios que nos den la información del libro (BookService).
Para obtener el id del libro, accedemos al método snapshot.params['id'] del objeto de la clase ActivatedRoute para acceder a los parámetros.

No olvidarse de hacer los import

Con el botón ejecutamos la función getBooks que utiliza a Router para volver a '/books' con el metodo navigate (podriamos acceder a cualquier otra direccion disponible en el route)
O sea navegamos con un objeto de la clase Router

...
import { Router, ActivateRouter } from '@angular/router'
@Component({
  template: `<h2>{{book.title}}</h2>
    <div><label>Id: </label>{{book.id}}</div>
    <div><label>Description:</label></div>
<p><button (click)="gotoBooks()">Back</button></p>
})
export class BookDetailComponent {
  book: Book;
  constructor (private router:Router, activatedRoute:ActivatedRoute, bookService:BookService) {
    let id=activatedRoute.snapshot.params['id'];
    this.book=book-service.getBook(id);
  }
  getBooks() { this.router.navigate (['/books']); }
}

6. Las rutas ' ' y '**'

Estas rutas son para indicar la ruta por omisión y la ruta cuando se inidica desde la aplicación una ruta que no existe. Por ejemplo http://localhost:3030 apuntaria a la route indicada por ' ' ,mientras que si nuestra  aplicación hace un this.router.navigate (['/direccion_inexistente']), cogería la route indicada pro '**', evitando lanzar una excepción de ruta no encontrada

const appRoutes = [
  { path: 'books/:id', component: BookDetailComponent },
  { path: 'books', component: BookDetailComponent },
  { path: '', redirectTo: 'books', pathMatch:'full' },
  { path: '**', component: PageNotComponent  }
]

jueves, 15 de diciembre de 2016

Angular 2. Entrega 13. Servicios

El servicio es otro elemento clave de Angular2. Veamos como nos justifica Micael Gallego el uso de servicios.

  • No es buena práctica meter en el componente la lógica de las peticiones http.
  • El componente podría llegar a ser muy complejo
  • Si el componente tiene muchas responsabilidades, los test se vuelven muy complejos.
  • Es mejor modularizar la aplicación a componentes con una única responsabilidad.
  • Es aconsejable desacoplar la aplicación.


Un servicio debe ser fácil de testear, y por tanto facimente manejable por el inyector de dependencias. También se les puede dar la propiedad de ser únicas o singleton (solo puede haber un único objeto de esa clase en toda la aplicación).

Los elementos que no tienen interfaz de usuario son servicios. Hay muchos servicios predefinidos como Http, servicios de login, que tiene acceso a la REST API.

Para que sea más sencillo implementar test. los servicios se inyectan en los componentes.


1. Implementación de un servicio


  • Se crea una clase para el servicio.
  • Se anota dicha clase con @Inyectable().
  • Se indica el servicio en la lista de providers del NgModule.
  • Se pone como parámetro en el constructor del componente que usa el servicio.

Veamos el código del servicio books.service.ts (observar el nombre del fichero que tiene "service")

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

@Injectable ()
export class BooksService {
  getBooks(title: string) {
    return [ 'Java One', 'Java Two','Java en 10 minutos'];
  }
}

Veamos ahora el componente ( app.component.ts) que hace uso del servicio

import { Component } from '@angular/core';
import {BooksService } from './books.service';

@Component ({
  selector: 'app-root',
  templateUrl: './app.component.html'
})

export class AppComponent {
  private books: string[] = [];
  
  constructor(private booksService : BooksService)()

  serach (title: string) {
    this.bools = this.bookService.getBooks(title);
  }
}

Por defecto, todos los servicios son singleton (se puede configurar para que no lo sean). Y por tanto se pueden utilizar para guardar el ESTADO de la aplicación (tada la información esta custodiada en uno o varios servicios con independencia de que tengamos múltiples componentes. Por ejemplo un servicio puede guardar el estado del login, otro que indica en que ventana estamos etc.

Veamos el NgModule, donde hay que definir el servicio en provider y en el import

import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { HttpModule, JsonModule } from '@angular/http';

import {AppComponent } from './app.component';
import {BooksService } from './books.service';

@NgModule ({
  declarations: [AppComponent],
  imports: [BrowserModule, FormsModule, HttpModule, JsonModule],
  bootstrap: [appcomponent],
  providers: [BooksService}
})
export class AppModule {}


2. Y si nuestro servicio no queremos que sea singleton ....

Podemos hacer que un servicio sea exclusivo para un componente y sus hijos, o sea, que NO sea singleton. O sea que si hay varias intancias de un componente o hijo, que haya varias intancias del servicio. Para ello se declara el servicio en el elemento providers del componente en vez del ngModel. Ahora queda disponible para este componene y sus hijos. Quedando nuestro componente

import { Component } from '@angular/core';
import {BooksService } from './books.service';

@Component ({
  selector: 'app-root',
  templateUrl: './app.component.html',
  providers: 'BookService'
})

export class AppComponent {
  private books: string[] = [];
  
  constructor(private booksService : BooksService)()

  serach (title: string) {
    this.bools = this.bookService.getBooks(title);
  }
}

3. Problemas con la ejecución asíncrona.

Http actua de forma asíncrona que significa que:

  • No puede devolver la información de forma inmediata.
  • Solo puede devolver información cuando llega la respuesta del servidor
  • En javaScript los métodos no se pueden bloquear esperando la respuesta.
  • Son asíncronos/reactivos.
Este código de llamada al servicio NO ES CORRECTO, ya que llama de forma sincrona (bloqueante) a un serviio asíncrono.

private service : BooksService = ...
let books = this.booksService.getBooks(title);
console.log(books);

Hay 3 formas de implementar un servicio con operaciones asíncronas en JavaScript:
  1. Callbacks: Se pasa como parámetro una función(de callback) que será ejecutada cuando llegue el resultado. Esta función recibe como resultado el error (si ha habido). No se recomienda.
  2. Promesas: El método devuelve un objeto Promise. Con el método then de este objeto, se define la función a ejecutar cuando llegue el resultado. Con el método catch se define la función a ejecutar si hay error. La promesa o se completa bien o se completa con error. Es la forma estandard de ES6.
  3. Observables: Tienen el método subscribe que contine las 2 funciones que serán ejecutadas (1) cuando llegue el resultado o (2) si se produce el error. Se implementa en RxJS (reactive extensions for Javascript) y es la recomendada en angular2. El resultado es un objeto de alto nivel

Veamos un ejemplo de Callback:

service.getBooks(title, (error, books)=> {
  if (error) {
    return console.error(error);
  }
  console.log(books);
});


Veamos un ejemplo de Promesa que parece ser mejor solución que el callback:

service.getBooks(title)
  .then  (books => console.log(books))
  .catch (error => console.error(error));
  
  
En el caso de Observables:

service.getBooks(title).subscribe(
   books => console.log(books),   // Función (1): si va todo bien
   error => console.error(error)  // Función (2): si hay error
);

4. El método map

El método map permite la transmisión de asincronia como si fuera un "pipe".
En este ejemplo, en vez de subscribe, utilizamos map, donde hacemos un return del objeto pero transformándolo para devolver solo los títulos, para ello utilizamos un lambda.

Veamos como queda el servicio books.service.ts . Para ello hacemos un import de 'rxjs/Rx' para utilizar observables (en concreto el método map), y utilizamos el método map (en 2 funciones) para trasformar el objeto Response a otro de alto nivel, pero hay que tener en cuenta que el map se debe utilizar solo en el servicio. y cuando ejecutemos este servicio en el componente, debemos utilizar

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import 'rxjs/Rx'; // forma de Typescript para importar libreria. Ahora es reactive

@Injectable()
export class BooksService {
  constructor (private http: Http) { }
  getBooks (title: string) {
    let url = "https://www.googleapis.com/books/v1/volumens?q=intitle:" + title;
    return this.http.get(url).map {
      response=> this.extractTitles(response); //extreae títulos de la respuesta con un lambda
    }
  }
  private extractTitles(response: Response) {
     //volvemos a utilizar el map como si fuera un pipe para extraer solo los títulos.
     return response.json().items.map( book => book.volumeInfo.title);
  }
}


Para el compomente app.component.ts tenemos utilizando observables, que como ya tenemos el response transformado en alto nivel en el servicio gracias al efecto "pipe" del map, ahora, se puede utilizar el subscribe :

import { Componet } from '@angular/core';
import { BooksService} from './books.service';


@Component({
  selector; app-root',
  templateUrl: './app.compopnent.html'
});
export class AppComponent {
  private books: string[]=[];
  constructor (private booksService: BooksService) {} // desaparece el private http
  search(title: string) {
       this.books = [];
    this.booksService.getBooks(title).subscribe (
      books => this.books =books , // cuando llega la respuesta. acvtualiza el array de books
      error => console.error(error)   
    );
  }
  
}


5. Manipular el error en observables RxJS con map y catch

También podemos transformar el error a "alto nivel" en los observables RxJS (map), para ello, tras una petición REST (get) en este caso, se dispone de una estructura muy similar a las promesas, con (1) con .map y (2) con .catch .
Con catch gestionamos el error y podemos devolver un nuevo error o simular una resouesta correcta (con un valor por defecto).  En el caso de lanzar un nuevo error se hace utilizando el método de clase Observable.throw Veamos:

getBooks(title: string) {
  let url= ...
  return this.http.get(url)
    .map   (response => this.extractTitles(response))
    .catch (error    => Observable.throw('Server error')) // Lanzamos nuevo error
}


6. Estados en los servicios http

Los estados en los servicios http pueden ser:

1. Stateles (sin estado)

  • No guardan información
  • Sus métodos devuleven valores, pero no cambian el estado del servicio.
  • ejemplo BooksService con llamadas a Google.

2.Statefull (con estado)

  • Mantienen su estado, guardan su información
  • Al ejecutar sus métodos, cambian su estado interno y también pueden devolver valores.
  • ejemplo BooksService con información en memoria.

¿Cual utilizar?

  • Los stateless son más fáciles de implementar porque encapsulan las peticiones REST al backend.
  • Los stateless son menos eficientes ya que cada vez hay que volver a pedir la información al backend
  • Los statefull son mas difíciles ya que hay que definir una política de sincronización entre el frontend y backend.
  • Los statefull son mas eficientes, ya que solo consultan al backend cuando hace falta.

7. Otras consideraciones

Del ejemplo de Micael Gallego, aparecen en app.component.ts algunas cosas:

  • ngOnInit() { this.refresh;} // este método parece ser que es para inicializar el componente
  • this.ItemService.getItems().subscribe(result =>this.refresh()); // ignoramos el resultado del servicio.



Angular 2. Entrega 12. Cliente REST e inyección de dependencias

Seguimos repitiendo parte de lo que se dijo en curso de Micael Gallego. En este caso hablamos de REST. Comencemos por pasos

1. La clase Http

El código para utilizar Http podría ser:

Http http = ...
http.get(url).subscribe(
  response => console.log(response.json()),
  error => console.error (error)
);


En este caso utilizamos el método get de la REST API.
No se utilizan promesas sino observables.
Hacemos uns subscribe al objeto que te devuelve el get.
Al método subscribe le pasamos 2 funciones:

  1. La que devuelve el resultado correcto response
  2. La que devuelve error.

La función response supone en este caso que recibinos un json. Si se devolviera otra cosa nos daría error.

Observamos que NO hay un new del objeto http. En un test es dificil reemplazar el objeto rest por uno "fake" o "mock" (simulador o sustituto), por eso no se hace el new


2. Inyección de dependencias.

Para hacer tests, viene en nuestra ayuda el inyector de dependencias. Si queremos utilizar un objeto externo, delegamos en el framework para que te pase el componente que solicitamos. La ventaja de esto es que el framework sabrá que estas en modo testing y te proporcionará una dependencia del modo test, que será distinta a la que te proporcionará en modo real.
Por tanto en un test, el objeto http sustituto (mock) hará lo que queramos sin hacer peticiones reales.

Lo que necesitamos para que se inyecte la dependencia es que le indiquemos al constructor como parámetro de construcción un tipo de objeto determinado. No olvidemos importar las dependencias (en este caso Http). En este caso temenos una función constructor que recibe un parámetro Http.

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

@Component{
  selector: 'app-root',
  templateUrl: './app.component,html'
)}

export class AppComponent {
  constructor (private http: Http) {}
  search(title:string) {
    ...
  )
}


Y como siempre hay que advertir al module también las dependencias que tenemos.

import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { HttpModule, JsonModule } from '@angular/http';

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

@NgModule ({
  declarations: [AppComponent],
  imports: [BrowserModule, FormsModule, HttpModule, JsonModule],
  bootstrap: [appcomponent]
})
export class AppModule {}

Si se devolviera XML, en vez de json tendríamos que indicarlo también en las dependencias.


3. Ejemplo

Vamos a llamar a una API pública de REST que saca libros. Veamos el app.component.html

<h1>Google Books</h1>

<input #title type="text">

<button  (click)="search(title.value); title.value=''">
  Buscar
</button>

<p *ngFor="let book of books">{{book}}</p>

Aquí, le pasamos al método search. la información que contiene el input, y despues vaciamos el contenido del input. Después mostramos todos los libros encontrados.

Veamos el componente app.component.ts, que tiene todo el jugo

<import {Component} from '@angular/core';
<import {Http) from '@angular/http';

@Component {
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
  
)}

export class AppComponet {
  
  private books: string[] = [];
  
  constructor (private http: Http) {}
  
  search(title: string) {

    this.books =  [];

    let url = "https://www.googleapis.com/books/v1/volumes?q=intitle:" + title;

    //Hacemos una petición GET.Con subscribe ya no hace falta utilizar promesas
    this.http.get(url).subscribe(

      // Cuando todo va bien
      response => (
         // descargamos en memoria sobre el objeto data el resultado de la petición
         let data = response.json();
         for (var i = 0; i < data.items.length; i++) {
            let bookTitle = data.items[i].volumeInfo.title;
            this.books.push(bookTitle);
         }
      )
      
      //Cuando ha salido mal la petición
      error => console.error(error)
    );  
  }
}

Al aparecer dentro del constructor el objeto http como private, entonces ya es un atributo de la clase y se puede hacer referencia al el como this.http


Para amanizar la espera, se podría incluir un spinner antes de hacer la petición get, y despues ocultarlo dentro del response.


4. Peticiones POST y PUT

Con un ejemplo se ve mejor. Observar que los métodos post y pu se diferencian respecto a get en que les pasamos a parte de la url ,  también los datos. En el app.component.ts metemos este código

let data = {....}; //datos en json o xml

//tambien se hubiera podido poner put en vez de post
this.http.post(url, data).subscribe (
  response => console.log(response),
  error=> console.error(error)
);