Створення та повернення спостережуваних із сервісу Angular 2


132

Це більше питання "найкращої практики". Є три гравці: 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, що містить цей масив, і повернути його?

Відповіді:


159

ОНОВЛЕННЯ: 9/24/16 Кутовий 2.0 Стабільний

Це питання все ще отримує багато трафіку, тому я хотів його оновити. З божевіллям змін від Альфа, Бета та 7 кандидатів у РК я перестав оновлювати свої відповіді, поки вони не стали стабільними.

Це ідеальний випадок для використання предметів та 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що дозволяє наштовхувати на нього значення. В Альфа 45 вам потрібно це перетворити toRx(), але я знаю, що вони працювали над тим, щоб позбутися цього, тому в Альфа 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


Я спробував цей підхід, але отримав "Неможливо використовувати" новий "з виразом, типу якого не вистачає виклику або побудувати підпис" -error. Хтось має уявлення, що робити?
Спок

3
@Spock специфікація, здавалося, оновилася після цього оригінального запитання. Вам більше не потрібне "нове" для спостережуваного, оскільки це робить це для вас. Просто видаліть нове і дайте мені знати, що відбувається. Зараз я псуюся з деякими речами, якщо це також працює для вас, я
оновлю

1
Використовувати EventEmitterдля чого завгодно, але @Output()не відстороняє. Дивіться також stackoverflow.com/questions/34376854 / ...
Гюнтер Zöchbauer

@ GünterZöchbauer, Так, це зараз ... У той час це вже буде EventEmitters, але вони з тих пір стандартизовані на Rx Observables. Мій приклад, що спостерігається, як і раніше працює, але якщо ви збираєтесь використовувати приклад EventEmitter, то я пропонував використовувати безпосередньо теми: github.com/Reactive-Extensions/RxJS/blob/master/doc/api/…
Денніс Смолек

1
@maxisam Дякую за редагування, хоча відповідь була / відносно альфа-видалення "нове" для "Спостережуваного" зараз правильне
Денніс Смолек

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());
      }
    }

Редагувати: Для кутового 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.

Отже, ось мій підхід. Ми не можемо відійти від підписки (), і цього не хочемо. У такому ключі наша служба повернеться Observable<T>із спостерігачем, у якого є наш дорогоцінний вантаж. Від абонента ми ініціалізуємо змінну Observable<T>, і вона отримає службу Observable<T>. Далі ми підпишемося на цей об’єкт. Нарешті, ви отримуєте своє "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 з нашими властивостями в нашому класі, а іноді ми робимо там якісь спеціальні речі. Тож таке відображення може відбуватися в нашій службі. І, як правило, тому, що наш виклик служби буде використовуватися не один раз, але, ймовірно, в інших місцях нашого коду, нам не доведеться знову виконувати це відображення в якомусь компоненті. Більше того, що робити, якщо ми додамо нове поле для людей? ....


Я погоджуюся, що форматування має бути в сервісі, і я розмістив стандартний метод спостереження, але перевага Суб'єктів у сервісі полягає в тому, що на ньому можуть спрацьовувати інші функції. Якщо вам завжди потрібен лише прямий http-дзвінок, то я б застосував метод спостережуваного ..
Денніс Смолек

9

У файлі service.ts -

а. імпорт 'з' із спостережуваного / з
b. створити список json
c. повернути об’єкт json за допомогою Observable.of ()
Напр. -

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

Зверніть увагу, що ви використовуєте Оглядову # карту для перетворення неочищеного Responseоб'єкта, який ви базуєте, що базується, спостерігається для розбору представленого відповіді JSON.

Якщо я вас правильно зрозумів, ти хочеш mapзнову. Але цього разу перетворення цього сирого JSON у ваші екземпляри Model. Отже, ви б робили щось на кшталт:

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

Отже, ви почали з "Спостережуваного", який випромінює Responseоб'єкт, перетворив його на спостережуване, яке випромінює об'єкт проаналізованого JSON цієї відповіді, а потім перетворив це на ще одне спостережуване, яке перетворило цей сирий JSON у масив ваших моделей.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.