chamada_da_api_remota - VWJavascript/Alurapic GitHub Wiki
Chamada da API remota
Quem cria o back-end disponibiliza uma série de endereços, ou, no termo técnico mais correto, uma série de API's para a aplicação Angular ou por uma aplicação Android consumir. É o criador da API que nos informa quais URL's estão disponíveis e qual verbo empregar. Em nosso caso, sabemos que é o endereço localhost:3000/v1/fotos que retorna uma lista de fotos. O navegador obtém essa lista por meio de um pedido especial, utilizando um verbo padrão do mundo HTTP, o verbo GET.
O serviço Http possui uma função de mesmo nome:
// alurapic/client/app/app.component.ts
import { Component} from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
http.get('v1/fotos')
}
}
A função http.get recebe como parâmetro o endereço do servidor que desejamos consumir. E o resultado da função, será nossa lista de fotos? Ainda não, será um fluxo que nos levará até ela! No lugar de declarar a variável como fluxo, usarei o termo em inglês, stream:
// alurapic/client/app/app.component.ts
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
let stream = http.get('v1/fotos')
}
}
Então angular usa RxJS?
Mas o que realmente é esse stream (fluxo)? Ele é proveniente da Reactive Extensions for JavaScript (RxJS), criadas pela Microsoft Open Technologies, Inc em parceria com a comunidade open source. O RxJS é um conjunto de bibliotecas para compor programas assíncronos e baseados em eventos, usando coleções observáveis. Que tal continuarmos com a prática para entendermos a teoria?
Interagimos com um fluxo observável (observable stream) do RxJS através de suas funções. Usamos a função subscribe para que possamos "observar" os dados que são retornados, em nosso caso, a resposta do servidor:
// alurapic/client/app/app.component.ts
// código anterior omitido
constructor(http: Http) {
var stream = http.get('v1/fotos');
stream.subscribe(function(res) {
});
}
// código posterior omitido
A resposta que recebemos ainda não é nossa lista de fotos, mas um objeto no qual solicitamos essa lista no formato que for interessante para nós. Por exemplo, há a função res.text(), que retorna os dados como string e a res.json(), que realiza automaticamente o parser para nós do JSON retornado em um array de objetos:
// alurapic/client/app/app.component.ts
// código anterior omitido
constructor(http: Http) {
var stream = http.get('v1/fotos');
stream.subscribe(function(res) {
this.fotos = res.json();
});
}
// código posterior omitido
Perceba que no exemplo, estamos armazenando a lista de fotos no formato JSON em this.fotos, ou seja, na propriedade fotos de uma instância da nossa Foto. Porém, se você é estudioso de JavaScript, já sabe que isso não funcionará. Cada função em JavaScript define o contexto de seu this, ou seja, seu valor durante sua execução. Quando acessamos o this no contexto da função subscribe, ele referenciará Subscriber e não a instância da classe Foto. É por isso que this.foto não existe. Uma maneira de resolver esse problema do dinamismo do this é a seguinte:
// alurapic/client/app/app.component.ts
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
var that = this; // aqui o this do construtor é a instância da classe Foto
var stream = http.get('v1/fotos');
stream.subscribe(function(res) {
that.fotos = res.json(); // that é a instância da classe Foto
});
}
}
Veja que guardamos o this da função constructor em uma variável chamada that, poderia ser qualquer nome. Nesse caso, o contexto de execução é a nossa classe AppComponent. Agora, dentro da função subscribe, acessamos a variável that, que temos certeza que aponta para a instância da classe AppComponent.
Vamos testar? Perfeito, nossa lista é exibida! Mas podemos usar um recurso do ES6 que pode nos poupar essas linhas de código, as arrow functions.
Arrow functions e escopo léxico
Veja que você consegue enxergar uma flecha (no inglês, arrow) abaixo:
=>
Agora que você já sabe de onde as Arrow Functions tiraram seu nome, vamos utilizá-las em nosso código:
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
var stream = http.get('v1/fotos');
stream.subscribe(res => {
this.fotos = res.json();
});
}
}
Uma arrow function é uma função anônima que possui uma sintaxe mais curta, quando comparada com a function expressions que usamos antes. Porém, o seu diferencial não é apenas a sintaxe enxuta: toda arrow function compartilha o mesmo this léxico de seu escopo pai.
Pense comigo, qual é o this de constructor? A instância da classe AppComponent. Qual o this de subscribe agora? O de constructor. Sendo assim, o this de subscribe será o da instância da classe AppComponent. O mesmo da sua função pai.
Será que funciona? Vamos salvar as alterações e recarregar nossa página. Excelente, todas as imagens retornadas do nosso servidor são exibidas.
Podemos enxugar ainda mais nosso código, evitando a declaração da variável stream:
// alurapic/client/app/app.component.ts
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
http.get('v1/fotos')
.subscribe(res => {
this.fotos = res.json()
});
}
}
Nosso código é funcional, mas o RxJS permite realizar uma série de operações sobre esse fluxo de maneira encadeada. Que tal já disponibilizarmos para a função subscribe a lista de fotos já parseada? Podemos fazer isso usando a extensão map:
// alurapic/client/app/app.component.ts
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
http.get('v1/fotos')
.map(res => res.json())
.subscribe(fotos => {
this.fotos = fotos
});
}
}
Veja que na função map estamos executando a instrução res.json(). Apesar de não retornarmos um valor através de return, a lista já parseada está disponível como o primeiro parâmetro da função subscribe. É por isso que seu parâmetro foi renomeado para fotos, tornando nosso código mais legível.
Porém, um teste revelará que nosso código não funciona. Abrindo o console do browser temos a mensagem de erro:
ORIGINAL EXCEPTION: http.get(...).map is not a function
A função map não existe em nosso observable stream. Isso acontece porque apenas o core do RxJS é carregado e, se quisermos usar outras extensões, precisamos importá-las em nosso código. Para isso, vamos alterar nosso AppModule e importar as extensões. Diferente do que viemos fazendo, apenas importamos a extensão, sem precisarmos adicioná-la à propriedade imports do ngModule:
Vamos importar a extensão map:
// alurapic/client/app/app.module.ts
// importou a extensão map!
import 'rxjs/add/operator/map';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FotoModule } from './foto/foto.module';
import { HttpModule } from '@angular/http';
@NgModule({
imports: [ BrowserModule, FotoModule, HttpModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Podemos resumir ainda mais o nosso código, evitando o uso de bloco na função subscribe:
// alurapic/client/app/app.component.ts
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
http.get('v1/fotos')
.map(res => res.json())
.subscribe(fotos => this.fotos = fotos);
}
}
Mas, e se um erro acontecer? Não saberemos de nada, por isso é uma boa prática fazer um log de qualquer erro que aconteça em nossa aplicação. É por essa razão que o segundo parâmetro da função subscribe é uma função que será chamada apenas quando ocorrer um erro durante a requisição. Por enquanto vamos apenas logar no terminal, mas futuramente podemos exibir uma mensagem amigável para o usuário:
// alurapic/client/app/app.component.ts
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: './app.component.html'
})
export class AppComponent {
fotos: Object[] = [];
constructor(http: Http) {
http.get('v1/fotos')
.map(res => res.json())
.subscribe(
fotos => this.fotos = fotos,
erro => console.log(erro)
);
}
}