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)
            );
    }
}