Angular 2 Hizmetinden Gözlemlenebilir Oluşturma ve iade etme


132

Bu daha çok "en iyi uygulamalar" sorusudur. Üç oyuncu vardır: a Component, a Serviceve a Model. ComponentAradığını Servicebir veritabanından almak için veri. ServiceKullanıyor:

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

dönmek için Observable.

ComponentSadece abone olabilir Observable:

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

Ancak, gerçekten istediğim şey , veritabanından alınan verilerden oluşturulan Servicebir Array of Modelnesneyi döndürmektir Service. ComponentAbone olma yönteminde sadece bu diziyi oluşturabileceğini fark ettim , ancak hizmetin bunu yapıp Component.

Nasıl o diziyi içeren Serviceyeni bir tane yaratır Observableve onu geri döndürür?

Yanıtlar:


159

GÜNCELLEME: 9/24/16 Açısal 2.0 Kararlı

Bu soru hala çok fazla trafik alıyor, bu yüzden güncellemek istedim. Alfa, Beta ve 7 RC adaylarındaki değişikliklerin çılgınlığıyla, kararlı hale gelene kadar SO cevaplarımı güncellemeyi bıraktım.

Bu, Konular ve Yeniden Oynatma Konuları kullanmak için mükemmel bir durumdur.

Ben şahsen kullanım tercih ReplaySubject(1)yeni aboneler bile geç eklediğinizde son saklanan değer iletilmesine izin veren gibidir:

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

Böylece, geç eklemem veya daha sonra yüklemem gerekse bile her zaman en son aramayı alabilir ve geri aramayı kaçırma konusunda endişelenmeyebilirim.

Bu, aynı akışı aşağı doğru itmek için kullanmanıza da olanak tanır:

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

Peki ya aramayı yalnızca bir kez yapmanız gerektiğinden% 100 eminseniz? Konuları ve gözlemlenebilirleri açık bırakmak iyi değildir ama her zaman "Ya Olursa?"

AsyncSubject burada devreye girer.

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

Müthiş! Konuyu kapatmamıza rağmen yine de yüklediği son şeyle cevap verdi.

Başka bir şey de bu http çağrısına nasıl abone olduğumuz ve yanıtı nasıl ele aldığımızdır. Harita , yanıtı işlemek için harika.

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

Ama ya bu çağrıları iç içe geçirmemiz gerekirse? Evet, özel bir işlevi olan konuları kullanabilirsiniz:

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

Ama bu çok fazla ve bunu yapmak için bir işleve ihtiyacınız olduğu anlamına geliyor. FlatMap'e girin :

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

Tatlı, varverileri son http çağrısından alan bir gözlemlenebilir.

Tamam bu harika ama bir angular2 hizmeti istiyorum!

Anladım:

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

 }

Gözlemcilerin ve gözlemlenebilirlerin büyük bir hayranıyım, bu yüzden bu güncellemenin yardımcı olacağını umuyorum!

Orijinal Cevap

Bunun kullanmanın kullanımı durum olduğunu düşünüyorum gözlemlenebilir Konu veya .Angular2EventEmitter

Hizmetinizde EventEmitter, üzerine değerler aktarmanıza izin veren bir yaratırsınız. In Alpha 45 Birlikte dönüştürmek zorunda toRx(), ama bu kadar içinde ki kurtulmak için çalıştığını biliyor Alpha 46 basitçe dönmek mümkün olabilir EvenEmitter.

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

Bu şekilde, EventEmitterfarklı servis işlevlerinizin artık zorlayabileceği bir tek var.

Doğrudan bir görüşmeden gözlemlenebilir bir kişiye geri dönmek isterseniz, şöyle bir şey yapabilirsiniz:

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

Bu, bunu bileşende yapmanıza izin verir: peopleService.myHttpCall('path').subscribe(people => this.people = people);

Ve hizmetinizdeki aramanın sonuçlarıyla uğraşın.

EventEmitterDiğer bileşenlerden ona erişmem gerektiğinde akışı kendi başına oluşturmayı seviyorum , ancak her iki yolun da çalıştığını görebiliyordum ...

İşte olay yayıcılı temel bir hizmeti gösteren bir planlayıcı : Plunkr


Bu yaklaşımı denedim, ancak "türü bir çağrı veya imza oluşturma özelliği olmayan bir ifadeyle 'yeni' kullanılamaz" hatası aldım. Ne yapacağına dair bir fikri olan var mı?
Spock

3
@Spock, bu orijinal sorudan bu yana teknik özellik güncelleniyor gibi görünüyor. Bunu sizin için yaptığı için artık gözlemlenebilir için "yeni" ye ihtiyacınız yok. Sadece yenisini kaldırın ve ne olduğunu bana bildirin. Şimdi bazı şeylerle uğraşıyorum, sizin için de işe yararsa bu cevabı güncelleyeceğim
Dennis Smolek

1
EventEmitterHerhangi bir şey için kullanmak ama tavsiye @Output()edilmez. Ayrıca bkz. Stackoverflow.com/questions/34376854/…
Günter Zöchbauer

@ GünterZöchbauer, Evet şimdi ... O zamanlar her yerde EventEmitters olacaktı ama o zamandan beri Rx Observables üzerinde standart hale getirdiler. Gözlemlenebilir örneğim hala çalışıyor, ancak EventEmitter örneğini kullanacaksanız, Denekler'i doğrudan kullanmanızı öneririm: github.com/Reactive-Extensions/RxJS/blob/master/doc/api/…
Dennis Smolek

1
@maxisam Düzenleme için teşekkürler, ancak cevap Alfa ile ilişkili olsa da, Gözlemlenebilir için "yeni" yi kaldırmak şu anda doğru
Dennis Smolek

29

Bu, Angular2 belgelerinden kendi Gözlemlenebilirlerinizi nasıl oluşturup kullanabileceğinize dair bir örnektir :

Hizmet

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

Bileşen

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

Tam ve çalışan örnek şurada bulunabilir: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service


18

Eklemek isterim ki, yaratılan nesne statikse ve http üzerinden gelmiyorsa, bunun gibi bir şey yapılabilir:

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

Düzenleme: Açısal 7.xx eşlemesinin burada açıklandığı gibi boru () kullanılarak yapılması gerekir ( 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
      }
    }

cevabımdan gözlemciler ve statik veriler hakkındaki soruma: https://stackoverflow.com/a/35219772/986160


17

Partiye biraz geç kaldım, ancak yaklaşımımın EventEmitters ve Subject kullanımından yoksun olması avantajına sahip olduğunu düşünüyorum.

İşte benim yaklaşımım. Subscribe () 'dan uzaklaşamıyoruz ve istemiyoruz. Bu bağlamda, Observable<T>hizmetimiz değerli yükümüzün olduğu bir gözlemci ile geri dönecektir . Arayan kişiden, bir değişkeni Observable<T>başlatacağız ve o, hizmetin bilgilerini alacaktır Observable<T>. Sonra, bu nesneye abone olacağız. Sonunda "T" yi aldın! hizmetinizden.

Birincisi, bizim insan servisimiz, ama sizinki parametreleri geçmiyor, bu daha gerçekçi:

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


          });
   });
}

Tamam, gördüğünüz gibi, bir Observabletür "insan" iade ediyoruz . Yöntemin imzası bile öyle söylüyor! Biz sokmak-in _peoplebizim gözlemci içine nesne. Bu türe bir sonraki Bileşen'deki arayıcımızdan erişeceğiz!

Bileşende:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService){}

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

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

Global kuruluşumuzun başlatmak _peopleObservableolduğunu dönerek Observable<people>kızımız dan PeopleService. Daha sonra bu mülke abone oluyoruz. Son olarak, this.peopledata ( people) cevabımızı ayarladık .

Hizmetin bu şekilde tasarlanmasının tipik hizmete göre büyük bir avantajı vardır: harita (...) ve bileşen: "abone ol (...)" modeli. Gerçek dünyada, json'u sınıfımızdaki mülklerimize eşlememiz gerekir ve bazen orada bazı özel şeyler yaparız. Dolayısıyla bu haritalama hizmetimizde gerçekleşebilir. Ve tipik olarak, servis çağrımız bir kez kullanılmayacağı için, ancak muhtemelen kodumuzun başka yerlerinde, bu eşlemeyi bazı bileşenlerde tekrar yapmak zorunda değiliz. Üstelik insanlara yeni bir alan eklersek ne olur? ...


Biçimlendirmenin hizmette olması gerektiğini kabul ediyorum ve standart bir Gözlemlenebilir yöntem de yayınladım, ancak bir hizmetteki Konular'ın avantajı, diğer işlevlerin onu tetikleyebilmesidir. Her zaman yalnızca doğrudan http çağrısına ihtiyacınız varsa, o zaman Gözlemlenebilir yöntemini kullanırım ..
Dennis Smolek

9

Service.ts dosyasında -

a. gözlemlenebilirden '/' ithalatı
b. bir json listesi oluşturun
c. Observable.of () kullanarak json nesnesini döndür
. Ör. -

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

Hizmetin get işlevini çağırdığımız bileşende -

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

İyi iş @Anirban, ayrıca sadece (this.clientList) iadesini verebilir;
foo-baar

7

Temel Observable'ınızın yaydığı ham nesneyi JSON yanıtının ayrıştırılmış bir gösterimine dönüştürmek için Observable # map kullandığınıza dikkat edin Response.

Seni doğru anladıysam, maptekrar istersin . Ancak bu sefer, bu ham JSON'u Model. Yani şöyle bir şey yaparsınız:

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

Böylece, bir Responsenesne yayan bir Observable ile başladınız, bunu o cevabın ayrıştırılmış JSON'sinin bir nesnesini yayan bir gözlemlenebilir hale getirdiniz ve sonra bunu, o ham JSON'u bir model dizisine dönüştüren başka bir gözlemlenebilir hale getirdiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.