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 можно преобразовать данные из одного формата в другой.

results matching ""

    No results matching ""