Создание hacker news с angular 2 cli, rxjs и webpack, часть 2

Разработка /
Разработка: Создание hacker news с angular 2 cli, rxjs и webpack, часть 2
Первая часть.

RxJS и Observables

В Angular 2 для общения с сервером мы используем библиотеку RxJS, которая возвращает Observable с данными, или асинхронный поток данных. Вероятно, вы уже знакомы с концепцией Promise-ов и как с их помощью можно асинхронно получать данные. Observable получают данные подобно promise-ам, но при этом позволяют следить за потоком данных и реагировать на различные события с ним.

Разработка: Создание hacker news с angular 2 cli, rxjs и webpack, часть 2
Источник: Вступление в Реактивное Программирование, которое вы пропустили

На диаграмме изображены события, которые происходят при клике на кнопку. Обратите внимание, как этот поток испускает значения (представляющие события клика по кнопке), ошибку, а также событие завершения.

Концепция использования 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<any> {
    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, которые нам возвращает запрос.

<!-- stories.component.html -->

<div class="main-content">
  <ol>
    <li *ngFor="let item of items | slice:0:30" class="post">
      <item class="item-block" itemID="{{ item }}"></item>
    </li>
  </ol>
  <!-- ... -->
</div>

Запустив приложение, увидим список элементов с их id:

Разработка: Создание hacker news с angular 2 cli, rxjs и webpack, часть 2
Итак, мы получаем идентификатор каждого item, теперь добавим подписку на детали каждого элемента и для этого напишем новый метод:

// hackernews-api.service.ts

//...

fetchItem(id: number): Observable<any> {
  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.component.html -->

<div *ngIf="!item" class="loading-section">
  <!-- Сюда можно добавить индикатор загрузки, если очень надо :) </i> -->
</div>
<div *ngIf="item">
  <div class="item-laptop">
    <p>
      <a class="title" href="{{item.url}}">
        {{item.title}}
      </a>
      <span class="domain">{{item.url | domain}}</span>
    </p>
    <div class="subtext-laptop">
      {{item.score}} points by
      <a href="">{{item.by}}</a>
      {{ (item.time | amFromUnix) | amTimeAgo }}
      <a href="">
        <span *ngIf="item.descendants !== 0">
          {{item.descendants}}
          <span *ngIf="item.descendants === 1">comment</span>
          <span *ngIf="item.descendants > 1">comments</span>
        </span>
        <span *ngIf="item.descendants === 0">discuss</span>
      </a>
    </div>
  </div>
  <div class="item-mobile">
    <!-- Разметка только для мобильных устройств -->
  </div>
</div>

Здесь всё просто: мы подписываемся на соответствующий поток для каждого элемента. В разметке у нас есть индикатор загрузки, который виден до получения данных с сервера. При загрузке элемента из Observable будут показаны его детали. Здесь можно скачать файлы стилей компонента.

Код приложения на текущем этапе можно скачать здесь. Перезапустите приложение и увидим такую картину:

Разработка: Создание hacker news с angular 2 cli, rxjs и webpack, часть 2

Окончание.
0 комментариев
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.