Создание и возвращение наблюдаемого из службы Angular 2

Это скорее вопрос "лучшей практики". Есть три игрока: a Component, a Service и a Model. Component вызывает Service для получения данных из базы данных. Service использует:

this.people = http.get('api/people.json').map(res => res.json());

чтобы вернуть Observable.

Component может просто подписаться на Observable:

    peopleService.people
        .subscribe(people => this.people = people);
      }

Однако я действительно хочу, чтобы Service возвращал объекты Array of Model, которые были созданы из данных, полученных Service из базы данных. Я понял, что Component может просто создать этот массив в методе подписки, но я думаю, что было бы чище, если служба сделает это и сделает его доступным для Component.

Как Service создать новый Observable, содержащий этот массив, и вернуть это?

Ответ 1

ОБНОВЛЕНИЕ: 24.09.16 Angular 2.0 Стабильный

Этот вопрос по-прежнему получает много трафика, поэтому я хотел его обновить. Из-за безумия изменений от кандидатов в Alpha, Beta и 7 RC я прекратил обновлять свои ответы SO, пока они не стабилизировались.

Это идеальный случай для использования Subjects и ReplaySubjects

Лично я предпочитаю использовать ReplaySubject(1) поскольку он позволяет передавать последнее сохраненное значение, когда новые подписчики присоединяются, даже когда опаздывает:

let project = new ReplaySubject(1);

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result));

http.get('path/to/whatever/projects/1234').subscribe(result => {
    //push onto subject
    project.next(result));

    //add delayed subscription AFTER loaded
    setTimeout(()=> project.subscribe(result => console.log('Delayed Stream:', result)), 3000);
});

//Output
//Subscription Streaming: 1234
//*After load and delay*
//Delayed Stream: 1234

Таким образом, даже если я присоединяюсь поздно или мне нужно загрузить позже, я всегда могу получить последний звонок и не беспокоиться об отсутствии обратного вызова.

Это также позволяет вам использовать тот же поток для нажатия на:

project.next(5678);
//output
//Subscription Streaming: 5678

Но что, если вы на 100% уверены, что вам нужно сделать звонок только один раз? Оставлять открытые темы и наблюдаемые вещи нехорошо, но всегда есть что, "Что если?"

Это где AsyncSubject входит.

let project = new AsyncSubject();

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result),
                  err => console.log(err),
                  () => console.log('Completed'));

http.get('path/to/whatever/projects/1234').subscribe(result => {
    //push onto subject and complete
    project.next(result));
    project.complete();

    //add a subscription even though completed
    setTimeout(() => project.subscribe(project => console.log('Delayed Sub:', project)), 2000);
});

//Output
//Subscription Streaming: 1234
//Completed
//*After delay and completed*
//Delayed Sub: 1234

Потрясающие! Несмотря на то, что мы закрыли тему, она все равно ответила последней загруженной информацией.

Другое дело, как мы подписались на этот http-вызов и обработали ответ. Карта отлично подходит для обработки ответа.

public call = http.get(whatever).map(res => res.json())

Но что, если нам нужно было вложить эти звонки? Да, вы можете использовать предметы со специальной функцией:

getThing() {
    resultSubject = new ReplaySubject(1);

    http.get('path').subscribe(result1 => {
        http.get('other/path/' + result1).get.subscribe(response2 => {
            http.get('another/' + response2).subscribe(res3 => resultSubject.next(res3))
        })
    })
    return resultSubject;
}
var myThing = getThing();

Но это много и означает, что вам нужна функция, чтобы сделать это. Введите FlatMap:

var myThing = http.get('path').flatMap(result1 => 
                    http.get('other/' + result1).flatMap(response2 => 
                        http.get('another/' + response2)));

Милый, var является наблюдаемой, которая получает данные из последнего http-вызова.

ОК, это здорово, но я хочу услугу angular2!

Понял тебя:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { ReplaySubject } from 'rxjs';

@Injectable()
export class ProjectService {

  public activeProject:ReplaySubject<any> = new ReplaySubject(1);

  constructor(private http: Http) {}

  //load the project
  public load(projectId) {
    console.log('Loading Project:' + projectId, Date.now());
    this.http.get('/projects/' + projectId).subscribe(res => this.activeProject.next(res));
    return this.activeProject;
  }

 }

 //component

@Component({
    selector: 'nav',
    template: '<div>{{project?.name}}<a (click)="load('1234')">Load 1234</a></div>'
})
 export class navComponent implements OnInit {
    public project:any;

    constructor(private projectService:ProjectService) {}

    ngOnInit() {
        this.projectService.activeProject.subscribe(active => this.project = active);
    }

    public load(projectId:string) {
        this.projectService.load(projectId);
    }

 }

Я большой поклонник наблюдателей и наблюдаемых, поэтому я надеюсь, что это обновление поможет!

Оригинальный ответ

Я думаю, что это использование случай использования Observable Subject или в Angular2 в EventEmitter.

В вашем сервисе вы создаете EventEmitter который позволяет вам помещать в него значения. В Alpha 45 вы должны конвертировать его с помощью toRx(), но я знаю, что они работали, чтобы избавиться от этого, поэтому в Alpha 46 вы можете просто вернуть EvenEmitter.

class EventService {
  _emitter: EventEmitter = new EventEmitter();
  rxEmitter: any;
  constructor() {
    this.rxEmitter = this._emitter.toRx();
  }
  doSomething(data){
    this.rxEmitter.next(data);
  }
}

Таким образом, имеется единственный EventEmitter который теперь могут распространяться ваши различные сервисные функции.

Если вы хотите вернуть наблюдаемое непосредственно из вызова, вы можете сделать что-то вроде этого:

myHttpCall(path) {
    return Observable.create(observer => {
        http.get(path).map(res => res.json()).subscribe((result) => {
            //do something with result. 
            var newResultArray = mySpecialArrayFunction(result);
            observer.next(newResultArray);
            //call complete if you want to close this stream (like a promise)
            observer.complete();
        });
    });
}

Это позволит вам сделать это в компоненте: peopleService.myHttpCall('path').subscribe(people => this.people = people);

И связывайтесь с результатами звонка в ваш сервис.

Мне нравится создавать поток EventEmitter по себе, если мне нужно получить доступ к нему из других компонентов, но я мог видеть, как работают оба пути...

Вот плункер, который показывает базовый сервис с источником событий: Plunkr

Ответ 2

Это пример из Angular2 docs того, как вы можете создавать и использовать свои собственные Observables:

Служба

import {Injectable} from 'angular2/core'
import {Subject}    from 'rxjs/Subject';
@Injectable()
export class MissionService {
  private _missionAnnouncedSource = new Subject<string>();
  missionAnnounced$ = this._missionAnnouncedSource.asObservable();

  announceMission(mission: string) {
    this._missionAnnouncedSource.next(mission)
  }
}

Компонент

    import {Component}          from 'angular2/core';
    import {MissionService}     from './mission.service';

    export class MissionControlComponent {
      mission: string;

      constructor(private missionService: MissionService) {

        missionService.missionAnnounced$.subscribe(
          mission => {
            this.mission = mission;
          })
      }

      announce() {
        this.missionService.announceMission('some mission name');
      }
    }

Полный и рабочий пример можно найти здесь: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Ответ 3

Я хотел бы добавить, что если созданный объект является статическим и не проходит через http, то можно сделать что-то подобное:

public fetchModel(uuid: string = undefined): Observable<string> {
      if(!uuid) { //static data
        return Observable.of(new TestModel()).map(o => JSON.stringify(o));
      }
      else {
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .map(res => res.text());
      }
    }

Изменить: Для Angular 7.xx сопоставление должно быть выполнено с использованием pipe(), как описано здесь (fooobar.com/questions/231026/...):

import {of,  Observable } from 'rxjs';
import { map } from 'rxjs/operators';
[...]
public fetchModel(uuid: string = undefined): Observable<string> {
      if(!uuid) { //static data
        return of(new TestModel());
      }
      else {
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .pipe(map((res:any) => res)) //already contains json
      }
    }

от ответа на мой вопрос о наблюдателях и статических данных: fooobar.com/questions/63634/...

Ответ 4

Я немного опаздываю на вечеринку, но я думаю, что мой подход имеет то преимущество, что ему не хватает использования EventEmitters и Subjects.

Итак, вот мой подход. Мы не можем уйти от подписки(), и мы этого не хотим. В этом ключе наш сервис вернет Observable<T> с наблюдателем, у которого есть наш драгоценный груз. От вызывающего мы инициализируем переменную Observable<T>, и она получит услугу Observable<T>. Затем мы подпишем этот объект. Наконец, вы получаете свой "Т"! от вашего сервиса.

Во-первых, наша служба людей, но ваша не передает параметры, что более реалистично:

people(hairColor: string): Observable<People> {
   this.url = "api/" + hairColor + "/people.json";

   return Observable.create(observer => {
      http.get(this.url)
          .map(res => res.json())
          .subscribe((data) => {
             this._people = data

             observer.next(this._people);
             observer.complete();


          });
   });
}

Хорошо, как вы можете видеть, мы возвращаем Observable типа "люди". Подпись метода, даже так говорит! Мы вставляем объект _people в нашего наблюдателя. Мы будем обращаться к этому типу от нашего вызывающего абонента в Компоненте, затем!

В компоненте:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService){}

getPeople(hairColor:string) {
   this._peopleObservable = this.peopleService.people(hairColor);

   this._peopleObservable.subscribe((data) => {
      this.people = data;
   });
}

Мы инициализируем наш _peopleObservable, возвращая это Observable<people> из нашего PeopleService. Затем мы подпишем это свойство. Наконец, мы устанавливаем this.people на наш ответ (people).

Архитектура службы таким образом имеет одно важное преимущество над типичной услугой: карта (...) и компонент: шаблон "подписаться (...)". В реальном мире нам нужно сопоставить json с нашими свойствами в нашем классе, и иногда мы делаем некоторые вещи. Таким образом, это сопоставление может произойти в нашем сервисе. И, как правило, потому что наш служебный вызов будет использоваться не один раз, но, возможно, в других местах нашего кода, мы не должны снова выполнять это сопоставление в некотором компоненте. Более того, что, если мы добавим новое поле людям?....

Ответ 5

Обратите внимание, что вы используете Observable # map для преобразования необработанного объекта Response, который ваша база Observable испускает в разобранное представление ответа JSON.

Если вы правильно поняли, вы хотите снова map. Но на этот раз, преобразование этого необработанного JSON в экземпляры вашего Model. Таким образом, вы бы сделали что-то вроде:

http.get('api/people.json')
  .map(res => res.json())
  .map(peopleData => peopleData.map(personData => new Person(personData)))

Итак, вы начали с Observable, который испускает объект Response, превратил это в наблюдаемое, которое испускает объект разбора JSON этого ответа, а затем превратил это в еще одно наблюдаемое, которое превратило этот необработанный JSON в массив ваших моделей.

Ответ 6

В файле service.ts -

а. импортировать 'из' наблюдаемого/из  б. создать список json
 с. return json object с помощью Observable.of()
  Ex. -

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

@Injectable()
export class ClientListService {
    private clientList;

    constructor() {
        this.clientList = [
            {name: 'abc', address: 'Railpar'},
            {name: 'def', address: 'Railpar 2'},
            {name: 'ghi', address: 'Panagarh'},
            {name: 'jkl', address: 'Panagarh 2'},
        ];
    }

    getClientList () {
        return Observable.of(this.clientList);
    }
};

В компоненте, где мы вызываем функцию get службы -

this.clientListService.getClientList().subscribe(res => this.clientList = res);