HTTP
HTTP является основным протоколом для обмена данными между браузером и сервером.
Современные браузеры поддерживают два API-интерфейса на базе HTTP: XMLHttpRequest (XHR) и JSONP. Некоторые браузеров поддерживают Fetch.
Для взаимодействия с сервером и вообще для работы с протоколом http нам нужен пакет "@angular/http", поэтому данный пакет должен быть указан среди зависимостей в файле конфигурацииpackage.config
И также в файле модуля AppModule должен быть импортирован класс HttpModule
из пакета "@angular/http":
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HttpModule } from '@angular/http';
@NgModule({
imports: [ BrowserModule, FormsModule, HttpModule],
declarations: [ AppComponent],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Ключевым для взаимодействия с сервером является класс Http. Этот класс определяет ряд методов для отправки различного рода запросов: GET, POST, PUT, DELETE. То есть он поддерживает полноценное взаимодействие с приложением на стороне сервера, которое реализует архитектуру REST.
В папку app/assets/mock-data добавим файл user.json, который будет представлять данные:
{
"name": "Bob",
"age": 28
}
Для представления данных в папку app/ добавим новый файл user.ts и определим в нем следующий код:
export class User{
public name: string;
public age: number;
}
При взаимодействии с сервером, как правило, обращения к серверу происходят не непосредственно из компонента, а из вспомогательных сервисов. Компоненты же выступают в качестве потребителей данных, которые получены от сервисов. Поэтому для работы с http добавим в папку app/ новый файл user.service.ts со следующим содержимым:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
@Injectable()
export class UserService {
constructor(private http: Http) { }
public getUser() {
return this.http.get('assets/mock-data/user.json')
}
}
Для отправки запросов сервис получает объект Http (который должен быть импортирован в главном модуле приложения AppModule, как показано выше). Поскольку сервис принимает в конструкторе параметр через механизм dependency injection, то к классу применяется декоратор@Injectable
.
Для выполнения get-запроса у объекта Http вызывается методget()
, в который передается адрес запроса - в нашем случае json-файл с данными.
Теперь используем этот сервис в компоненте AppComponent
import { Component, OnInit} from '@angular/core';
import { Response } from '@angular/http';
import { UserService } from './user.service';
import { User } from './user';
@Component({
selector: 'my-app',
template: `<div>
<p>Имя пользователя: {{user?.name}}</p>
<p>Возраст пользователя: {{user?.age}}</p>
</div>`,
providers: [ HttpService ]
})
export class AppComponent implements OnInit {
user: User;
constructor(private userService: UserService){}
ngOnInit() {
this.userService.getUser().subscribe((data: Response) => {
this.user = data.json();
console.log(this.user);
});
}
}
В данном случае в шаблоне выводятся данные объекта User, которые мы хотим получить с сервера. Однако загрузка данных, скажем, в конструкторе компонента не очень желательна. В этом плане методngOnInit()
, который определен в интерфейсе OnInit и который вызывается при инициализации компонента представляет более предпочтительное место для загрузки данных.
Так как ранее созданный сервис HttpService был определен в коллекции провайдеров:
providers: [HttpService]
Далее в методе ngOnInit()
получаем данные из сервиса. Сам метод http.get()
возвращает объект Observable<Response>
, где объект Response инкапсулирует полученные данные в формате json. Observable представляет своего рода поток, и для прослушивания событий из этого потока применяется метод subscribe
. Этот метод определяет действие над результатом запроса - объектом Response. В данном случае действие определено в виде лямбда-выражения. А чтобы получить сами данные, к объекту Response надо применить методjson()
Причем в данном случае определение json-файла соответствует определению класса User, поэтому простое присвоение
this.user=data.json()
пройдет успешно.
Подобным образом мы можем загружать и другие более сложные данные. Например, создаим еще один файл users.json в app/assets/mock-data:
[{
"name": "Bob",
"age": 28
},{
"name": "Tom",
"age": 45
},{
"name": "Alice",
"age": 32
}]
Создадим в классе UserService еще один метод для получения списка юзеров:
public getUsersList() {
return this.http.get('assets/mock-data/users.json');
}
И изменим код компонента:
...
public users: User[] = [];
...
public ngOnInit() {
...
this.userService.getUsersList().subscribe((data: Response) => {
this.users = data.json();
console.log(this.users);
});
}
Преобразование данных
В предыдущих случаях все было относительно просто: определение файла user.json представляло объект User, а файл users.json определял массив объектов User. Причем, ключи в json-файле соответствовали названиям свойств класса User. То есть было прямое соответствие. Но такое случается не всегда. Даже наоборот - как правило, структуры данных в приложении могут отличаться от структуры полученных данных. Например, изменим файл users.json следующим образом:
{
"data":
[{
"userName": "Bob",
"userAge": 28
},{
"userName": "Tom",
"userAge": 45
},{
"userName": "Alice",
"userAge": 32
}]
}
В приложении нам по прежнему необходимо получить массив объектов User. В этом случае необходимо произвести трансформацию данных при их получении.
Изменим модель User:
export class User {
public name: string;
public age: number;
constructor (data: Object) {
this.name = data.userName;
this.age = data.userAge;
}
}
Мы добавили конструктор, который принимает объект с данными и присвоили нужные нам значения.
Далее модернизируем сервис:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { User } from './user';
@Injectable()
export class UserService {
constructor(private http: Http) { }
public getUser() {
return this.http.get('assets/mock-data/user.json')
}
public getUsersList() {
return this.http.get('assets/mock-data/users.json')
.map((response: Response) => {
let data = response.json();
let results: User[] = [];
data.forEach(item => results.push(new User(item)));
return results;
});
}
}
Мы добавили метод map
для http запроса. Для того чтоб метод map
был доступен, необходимо импортировать его из библиотеки Rxjs - import 'rxjs/add/operator/map';
Это метод позвонит нам обработать данные и предать их в компонент.
Общение к этому в сервису из компонента так же потребует изменений:
this.userService.getUsersList().subscribe((data: User[]) => {
this.users = data;
console.log(this.users);
});
Раньше метод getUsersList() объект Response, инкапсулирующий данные ответа. В то же время это не является предпочтительным способом для организации приложения. Так как смысл использования специального сервиса для работы с http заключается в сокрытии деталей отправки запросов. Компонент же ожидает получить какие-то конкретные данные, например, в виде набора объектов User. С помощью метода map класса Observable можно преобразовать данные из одного формата в другой.