Создание и возврат Observable из службы Angular 2


132

Это скорее вопрос "лучших практик". Есть три игрока: а Component, а Serviceи а 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можно просто создать этот массив в методе subscribe, но я думаю, было бы чище, если бы служба сделала это и сделала бы его доступным для Component.

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

Ответы:


159

ОБНОВЛЕНИЕ: 24.09.16 Angular 2.0 Stable

Этот вопрос по-прежнему вызывает много трафика, поэтому я хотел его обновить. Из-за безумия изменений от кандидатов 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 или в .Angular2EventEmitter

В своем сервисе вы создаете объект, 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


Я пробовал этот подход, но получил ошибку «Невозможно использовать 'new' с выражением, тип которого не имеет сигнатуры вызова или конструкции». У кого-нибудь есть идеи, что делать?
Spock

3
@Spock, похоже, спецификация обновилась после этого исходного вопроса. Вам больше не нужно «новое» для наблюдаемого, поскольку оно делает это за вас. Просто удалите новый и дайте мне знать, что произойдет. Я сейчас возился с некоторыми вещами, если это
сработает и

1
Использование EventEmitterдля чего угодно, но @Output()не рекомендуется. См. Также stackoverflow.com/questions/34376854/…
Günter Zöchbauer

@ GünterZöchbauer, да, теперь ... В то время должны были быть EventEmitters повсюду, но с тех пор они стандартизированы на Rx Observables. Мой пример Observable все еще работает, но если вы собирались использовать приведенный мною пример
EventEmitter,

1
@maxisam Спасибо за редактирование, хотя ответ был / относительно Альфы, удаляющей «новый» для Observable, теперь верен
Деннис Смолек

29

Это пример из документации Angular2 о том, как вы можете создавать и использовать свои собственные 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


18

Я хотел бы добавить, что если создаваемый объект статичен и не проходит через 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 (), как описано здесь ( https://stackoverflow.com/a/54085359/986160 ):

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
      }
    }

из ответа на мой вопрос о наблюдателях и статических данных: https://stackoverflow.com/a/35219772/986160


17

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

Итак, вот мой подход. Мы не можем уйти от subscribe () и не хотим этого. В этом ключе наша служба вернет сообщение 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ответ data ( ).

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


Я согласен с тем, что форматирование должно быть в службе, и я также опубликовал стандартный метод Observable, но преимущество Subjects в службе заключается в том, что на нем могут запускаться другие функции. Если вам всегда нужен только прямой http-вызов, я бы использовал метод Observable ..
Деннис Смолек

9

В файле service.ts -

а. импорт 'of' из наблюдаемого / из
b. создать список json
c. вернуть объект json с помощью 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);

Хорошая работа @Anirban, также можно было вернуть только (this.clientList);
foo-baar

7

Обратите внимание, что вы используете 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 в массив ваших моделей.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.