http_e_injecao_de_dependencia - VWJavascript/Alurapic GitHub Wiki
É extremamente comum que uma aplicação feita em Angular consuma dados de um servidor por meio de uma API REST, que retorna esses dados na estrutura JSON. Nosso servidor já possui uma série de endpoints que utilizaremos ao longo do treinamento. Se você no final deste treinamento quiser saber como esse servidor é feito, há um treinamento específico. Bom, de todos os endpoints que o servidor possui, há um deles que nos interessa. Vamos vê-lo.
Abra seu navegador e digite o endereço (claro, seu servidor precisa estar de pé):
http://localhost:3000/v1/fotos
Pois bem, e se no lugar de abrir o endereço pelo navegador, nossa aplicação Angular realizasse essa tarefa para nós? Teríamos acesso a uma lista de fotos que pode substituir aquela que temos fixa em nosso componente. Se os dados mudam, a lista que é exibida para o usuário pelo nosso template também mudará. Perfeito, não?
Para que possamos acessar os dados do nosso servidor, precisaremos realizar requisições Ajax, aquelas que são assíncronas por natureza. Se você vem do mundo jQuery, já deve ter usado $.ajax ou uma de suas especializações. O Angular possui seu próprio serviço para executar este tipo de requisição, o http. Mas como teremos acesso a este serviço especial do Angular?
Vamos alterar alurapic/client/app/app.component.ts e importar a classe Http. Vamos aproveitar e alterar a propriedade fotos para passar a conter apenas um array vazio. Faz todo sentido, já que ela receberá uma lista de fotos vinda do nosso servidor local:
// 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 = [];
}
Apesar de termos importado Http, isso ainda não é suficiente, porque precisamos de uma instância dessa classe para nos comunicarmos com nosso servidor. Em ES6, toda classe possui um constructor, que é chamado toda vez que uma instância da classe é criada. Sendo assim, que tal criarmos uma instância de Http no construtor da nossa classe?
// 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 = [];
constructor() {
let http = new Http(); // não funciona, você verá o motivo!
}
}
Se você ainda não conhece a versão 6 do JavaScript, deve estar pensando que escrevi incorretamente a palavra chave let e que deveria ser var. Em ES6, a maneira mais recomendada é declarar variáveis com let. A palavra chave let declara uma variável com escopo de bloco, que só existirá no bloco em que foi declarada.
Antes do ES6, era possível criar um escopo de bloco através de técnicas de programação, que deixavam nosso código mais verboso e difícil de ler. A palavra chave let foi uma adição muito aplaudida pela comunidade.
Apesar dos nossos esforços, o código que escrevemos não funcionará. Isto porque a criação de uma instância de Http envolve muito mais do que simplesmente chamar seu construtor. Ou seja, a criação e preparação de uma instância dessa classe não é nada simples.
A boa notícia é que podemos nos livrar dos detalhes de criação do serviço Http, solicitando-o para o framework e gritando no ouvido dele "Eu quero um Http!". É claro que nada acontecerá se você ficar gritando na frente da sua tela, porém podemos traduzir esse berro em nosso código, ao adicionar nossa dependência no construtor da classe:
// 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 = [];
constructor(http) {
}
}
Será que funciona? Não, pois ao recarregarmos a página nossos componentes não são exibidos. E se abrirmos o console, vemos a seguinte mensagem de erro:
Error: Can't resolve all parameters for AppComponent: (?).(…)
O Angular está dizendo que não consegue resolver o parâmetro do construtor. Faz todo sentido, porque é ele que cria a instância de AppComponent e quando vê o parâmetro http não sabe o que fazer com ele. E agora? Precisamos dar uma pista para o Angular, deixando claro que precisamos que ele busque essa dependência. Fazemos isso através do decorator Inject:
// 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 = [];
constructor(@Inject(Http) http) {
}
}
Repare que @Inject recebe como parâmetro o tipo (classe) do serviço que importamos, o Http do módulo @angular/http. Como usamos a anotação antes do parâmetro do construtor, a variável http receberá o serviço Http. Não nos importa como o framework consegue instanciar este serviço, o que nos interessa é que ele está prontinho para uso.
Recarregando a página, recebemos outra mensagem de erro no console do Chrome. Olhando a segunda linha, vemos esta mensagem:
ORIGINAL EXCEPTION: No provider for Http!
Pois é, apesar de termos pedido para o Angular criar uma instância de Http para nós, parece que ele também não sabe criá-la! Vamos entender o que houve; a mensagem de erro indica que não há um provedor para Http. Provedores são serviços especializados na construção de objetos e que auxiliam o framework no processo de criação de objetos injetados, como o nosso. Em nosso caso, parece não existir nenhum provider que saiba construir Http para nós, uma pena. Mas nem tudo está perdido!
Podemos resolver isso importando o módulo HttpModule em AppModule. Este módulo já traz um provider configurado, que será usado pelo Angular toda vez que um objeto do tipo Http for injetado com o decorator Inject:
Vamos alterar alurapic/client/app/app.module.ts:
// alurapic/client/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FotoModule } from './foto/foto.module';
// importou o módulo que já possui um provider configurado
import { HttpModule } from '@angular/http';
// HttpModule adicionadno no array de imports!
@NgModule({
imports: [ BrowserModule, FotoModule, HttpModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Um novo teste demonstra que o título da nossa aplicação é exibido, o que é uma garantia de que nossa aplicação está funcionando.
Apesar de funcionar, podemos injetar nossa dependência de uma maneira menos verbosa com o uso do TypeScript.
O TypeScript não possui esse nome à toa. Podemos tipar estaticamente nossas variáveis e um dos benefícios desse processo é que a checagem de tipos nos ajuda a detectar problemas em tempo de desenvolvimento, inclusive auxiliar editores de textos e IDE's a autocompletarem nosso código. Angular se aproveita da tipagem do TypeScript para identificar nossas dependências, evitando assim o uso de @Inject, que aliás, recebe o tipo de quem queremos injetar como parâmetro.
A tipagem com TypeScript consiste em adicionar : seguido do tipo da variável, isto é, sua classe:
Mais simples do que a forma anterior com @Inject que utilizamos. Que tal a partir deste capítulo usarmos e abusarmos do sistema de tipos do TypeScript? Como seria a tipagem da propriedade fotos da nossa classe AppComponent? Sabemos que ela é um array que contém objetos, sendo assim, podemos fazer:
// 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: Array<Object> = [];
constructor(http: Http) {
}
}
Temos um atributo de visibilidade pública do tipo Array, onde cada elemento é do tipo Object. Contudo, podemos usar uma sintaxe menos verbosa para indicarmos que temos um array do tipo Object:
// alurapic/client/app/app.component
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) {
}
}
Sabendo como injetar dependências na definição de classes dos nossos componentes com a ajuda do framework, em uma sintaxe mais enxuta e com o uso do TypeScript, vamos usar o serviço Http.