RxJS и Observables
В Angular 2 для общения с сервером мы используем библиотеку RxJS, которая возвращает Observable с данными, или асинхронный поток данных. Вероятно, вы уже знакомы с концепцией Promise-ов и как с их помощью можно асинхронно получать данные. Observable получают данные подобно promise-ам, но при этом позволяют следить за потоком данных и реагировать на различные события с ним.
Источник: Вступление в Реактивное Программирование, которое вы пропустили
На диаграмме изображены события, которые происходят при клике на кнопку. Обратите внимание, как этот поток испускает значения (представляющие события клика по кнопке), ошибку, а также событие завершения.
Концепция использования Observable в приложениях известна как Реактивное Программирование.
Observable Data Service
Пришло время для получения реальных данных. Для этого нам нужно создать Observable Data Service и включить его в наши компоненты.
ng g service hackernews-api
Будет создан и настроен файл службы. А ещё нам следует разобраться с тем, как работает Hacker News API. Из документации понятно, что всё (опросы, комментарии, топики, вакансии) это элементы с различающимися id. И информация по конкретному элементу может быть получена по специальному адресу
// https://hacker-news.firebaseio.com/v0/item/2.json?print=pretty
{
"by" : "phyllis",
"descendants" : 0,
"id" : 2,
"kids" : [ 454411 ],
"score" : 16,
"time" : 1160418628,
"title" : "A Student's Guide to Startups",
"type" : "story",
"url" : "https://www.paulgraham.com/mit.html"
}
К примеру, если нам нужно получить такие данные, как рейтинги на главной страницы, необходимо использовать специальную конечную точку, близкую к топикам. И лучшие топики можно получить так:
// https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty
[ 12426766, 12426315, 12424656, 12425725, 12426064, 12427341, 12425692, 12425776, 12425324, 12425750, 12425135, 12427073, 12425632, 12423733, 12425720, 12427135, 12425683, 12423794, 12424987, 12423809, 12424738, 12425119, 12426759, 12425711, 12422891, 12424731, 12423742, 12424131, 12424184, 12422833, 12424421, 12426729, 12423373, 12421687, 12427437 ...]
Таким образом, мы получаем список топиков в топе, а затем необходимо пройтись по каждому из них. Приступим.
Включим службу в метаданные provider нашего NgModule:
// app.module.ts
//...
import { HackerNewsAPIService } from './hackernews-api.service';
@NgModule({
declarations: [
...
],
imports: [
...
],
providers: [HackerNewsAPIService],
bootstrap: [AppComponent]
})
export class AppModule { }
Теперь добавим метод для запроса в неё:
// hackernews-api.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
@Injectable()
export class HackerNewsAPIService {
baseUrl: string;
constructor(private http: Http) {
this.baseUrl = 'https://hacker-news.firebaseio.com/v0';
}
fetchStories(): Observable
return this.http.get(`${this.baseUrl}/topstories.json`)
.map(response => response.json());
}
}
Как мы говорили ранее, вызов http.get возвращает Observable с данными. В fetchStories мы принимаем Observable, а затем map-им его в формат JSON.
// stories.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HackerNewsAPIService } from '../hackernews-api.service';
@Component({
selector: 'app-stories',
templateUrl: './stories.component.html',
styleUrls: ['./stories.component.scss']
})
export class StoriesComponent implements OnInit {
items;
constructor(private _hackerNewsAPIService: HackerNewsAPIService) {}
ngOnInit() {
this._hackerNewsAPIService.fetchStories()
.subscribe(
items => this.items = items,
error => console.log('Error fetching stories'));
}
}
В хуке ngOnInit, который срабатывает при инициализации компонента, мы подписываемся (subscribe) на поток данных и присваиваем атрибуту items то, что нам будет возвращено. А в наше представление мы добавим SlicePipe для вывода только 30 элементов списка из 500, которые нам возвращает запрос.
Запустив приложение, увидим список элементов с их id:
Итак, мы получаем идентификатор каждого item, теперь добавим подписку на детали каждого элемента и для этого напишем новый метод:
// hackernews-api.service.ts
//...
fetchItem(id: number): Observable
return this.http.get(`${this.baseUrl}/item/${id}.json`)
.map(response => response.json());
}
Немного доработаем компонент item:
// item.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { HackerNewsAPIService } from '../hackernews-api.service';
@Component({
selector: 'item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.scss']
})
export class ItemComponent implements OnInit {
@Input() itemID: number;
item;
constructor(private _hackerNewsAPIService: HackerNewsAPIService) {}
ngOnInit() {
this._hackerNewsAPIService.fetchItem(this.itemID).subscribe(data => {
this.item = data;
}, error => console.log('Could not load item' + this.itemID));
}
}
{{item.title}}
{{item.url | domain}}
{{item.by}}
{{ (item.time | amFromUnix) | amTimeAgo }}
{{item.descendants}}
comment
1">comments
discuss
Здесь всё просто: мы подписываемся на соответствующий поток для каждого элемента. В разметке у нас есть индикатор загрузки, который виден до получения данных с сервера. При загрузке элемента из Observable будут показаны его детали. Здесь можно скачать файлы стилей компонента.
Код приложения на текущем этапе можно скачать здесь. Перезапустите приложение и увидим такую картину:
Разработчик: java, kotlin, c#, javascript, dart, 1C, python, php.
Пишите: @ighar. Buy me a coffee, please :).