2016-11-10 4 views
3

holen Ich habe einen Service, was mehrmals von vielen meiner Angular 2-Komponenten verwendet wird. Es holt Kundendaten aus einer Web-API und gibt einen beobachtbaren:Daten einmal mit Observables in Angular 2

getCustomers() { 
    return this.http 
     .get(this.baseURI + this.url) 
     .map((r: Response) => { 
      let a = r.json() as Customer[];      
      return a;       
     });    
}  

ich spritze diesen Dienst in meiner Stammkomponente und in jeder Komponente, die die Kunden zugreifen möchte ich abonnieren Sie beobachtbare:

this.customerService.getCustomers().subscribe(v => this.items = v); 

Jede Komponente, die mein Observable abonniert, verursacht jedoch eine andere Ausführung der HTTP-Anfrage. Aber die Daten nur einmal zu holen ist genug. Wenn ich Aktie versuchen(), ist es nicht mein Problem lösen:

getCustomers() { 
    return this.http 
     .get(this.baseURI + this.url) 
     .map((r: Response) => { 
      let a = r.json() as Customer[];      
      return a;       
     }).share();    
} 

Immer noch das gleiche Problem. Irgendwelche Vorschläge, welche Operatoren ich verwenden muss, um nur Daten einmal zu holen?

Antwort

5

1) Sie können einfach heruntergeladenen Daten in Ihrem Dienst speichern:

export class CustomersService { 
    protected _customers: Array<Customer>; 

    constructor(public http: Http) {} 

    public getCustomers(): Observable<Array<Customer>> { 
    return new Observable(observer => { 
     if (this._customers) { 
     observer.next(this._customers); 
     return observer.complete(); 
     } 
     this.http 
     .get(this.baseURI + this.url) 
     .map((r: Response) => (r.json() as Array<Customer>)) 
     .subscribe((customers: Array<Customer>) => { 
      this._customers = customers; 
      observer.next(this.customers); 
      observer.complete(); 
     }); 
    }); 
    } 
} 

2) Kürzere Ansatz refresh Parameter:

export class CustomersService { 
    protected _customers: Array<Customer>; 

    constructor(public http: Http) {} 

    public getCustomers(refresh?: boolean): Observable<Array<Customer>> { 
    if (!refresh && this._customers) { 
     return Observable.of(this._customers); 
    } 
    return this.http 
      .get(this.baseURI + this.url) 
      .map((c: Response) => (c.json() as Array<Customer>)) 
      .do((customers: Array<Customer>) => { 
       this._customers = customers; 
      }); 
    }); 
    } 
} 

3) ausnutzend ReplaySubject:

export class CustomersService { 
    protected _customers$: ReplaySubject<Array<Customer>> = new ReplaySubject(1); 
    protected _customersInitialized: boolean; 

    constructor(public http: Http) {} 

    public getCustomers(refresh?: boolean): Observable<Array<Customer>> { 
    if (refresh || !this._customersInitialized) { 
     this._customersInitialized = true; 
     return this.http 
     .get(this.baseURI + this.url) 
     .map((c: Response) => (c.json() as Array<Customer>)) 
     .subscribe((customers: Array<Customer>) => { 
      this._customers$.next(customers); 
     }); 
    } 
    return this._customers$.asObservable().skip(+refresh).distinctUntilChanged(); 
    } 
} 

Und dann:

this.customersService.getCustomers() 
    .subscribe(customers => this.customers = customers); 

Sie auch aussetzen kann das immer up-to-date customers Feld von SomeService für nur Zwecke lesen (wie Anzeigen in den Vorlagen) auf diese Weise:

public get customers(): ReadonlyArray<Customer> { 
    return this._customers; 
} 
+0

Mit ein wenig Modifikation funktioniert das. Sie müssen daran denken, dass innerhalb Ihres Versprechens nur ein Observable erstellt, aber nicht abonniert wird. Ihre Lösung funktioniert also nur, wenn Sie dieses erstellte Observable abonnieren und im Abonnement "resolve ..." aufrufen. – David

+0

@David Wie bist du dazu gekommen? –

+1

@NickDeBeer, ich habe meine Antwort aktualisiert. –

1

Der Share-Operator gibt die Möglichkeit, das gleiche Stream-Ergebnis mit mehreren Beobachtern zu verwenden. Es könnte gut sein, aber Sie generieren einen neuen beobachtbaren Stream jedes Mal, wenn Sie getCustomers() aufrufen, gibt es keinen Grund, share() aufzurufen, da Sie diesen Stream nicht mehrfach abonniert haben.

Wenn Sie die Daten mit mehreren Beobachtern teilen möchten, aber nur einen http-Aufruf machen, müssen Sie einfach einen zweiten Stream erstellen, den Sie mit dem http-Feed speisen, der die Daten enthält. Danach können alle Ihre Komponenten es abonnieren.

Der Code könnte so etwas wie die

@Injectable() 
class FooBar { 

    public dataStream:Subject<any> = new Subject(); 

    constructor(private http:Http) {} 

    public getCustomers() { 
     return this.http 
     .get(this.baseURI + this.url) 
     .map((response:Response) => response.json()) 
     .map((data) => { 
      this.dataStream.next(data); 
      return data; 
     }) 
    } 

} 


@Component({}) 
class BarFooHttpCaller { 
    constructor(private foobar:Foobar) {} 

    ngOnInit() { 
     this.foobar.getCustomers().subscribe(() => { console.log('http done') }); 
     this.foobar.dataStream.subscribe((data) => { 
      console.log('new data', data); 
     }) 
    } 
} 

@Component({}) 
class OtherBarFoo { 
    constructor(private foobar:Foobar) {} 

    ngOnInit() { 
     this.foobar.dataStream.subscribe((data) => { 
      console.log('new data', data); 
     }) 
    } 
} 
+0

okay, aber in diesem Fall muss ich immer an der Komponente starten, die GetCustomers() aufruft. abonnieren. (...) in meiner Anwendung kann ich nie wissen, welche Komponente zuerst zugegriffen wird (könnte ein Benutzer Geben Sie eine andere URL als ein anderer Benutzer ein. Wenn ich das Abonnement im Dienst selbst anrufe, funktioniert es nicht wirklich. Wie soll ich Ihr Beispiel in diesem Fall ändern? – David

+0

Sie haben viele Möglichkeiten, speichern Sie die Daten outs ide der dataStream und überprüfen Sie, dass es bereits feed ist, und wenn ja, geben Sie den dataStream anstelle von http ein. Sie können auch eine einfache Entprellung durchführen, die mehrere Aufrufe von getCustomers() prendieren kann, so dass Sie vermeiden könnten, auf die Nebenläufigkeit zu achten. Eine andere Methode wäre, die Methode in der Komponente Ihrer Top-Level-Route aufzurufen und einfach in den Childs zu abonnieren. – Polochon

1

sein ich einen übergeordneten Container schaffen würde, sobald die Daten holen, und übergeben es Komponenten Kind mit @Input.

Parent:

@Component({ 
    selector: 'BarFooHttpCaller', 
    template: ´<child *ngIf="data.length > 0" [data]></child>´ 
}) 

class BarFooHttpCaller { 
    private data: any; 
    constructor(private foobar:Foobar) { 
     this.data = {}; 
    } 

    ngOnInit() { 
     this.foobar.getCustomers().subscribe(() => {  
      console.log('httpdone') 
     }); 
     this.foobar.dataStream.subscribe((data) => { 
      console.log('new data', data); 
      this.data = data; 
     }) 
    } 
} 

Kind:

import { Component, Input } from '@angular/core'; 

@Component({ 
    selector: 'child', 
    template: ´<div>{{data}}</div>´ 
}) 

export class Child { 
    @Input() data: any; 

} 
0

Wenn Sie möchten, dass mehrere Kinder dieselbe Observable abonnieren, aber die Observable nur einmal ausführen, können Sie Folgendes tun.

Beachten Sie, dass dies auf die Gestaltung von Observablen anhaftet, da wir die beobachtbaren in der Dienstschicht (Observable.fromPromis (stream.toPromise()) ausgeführt werden, wenn die Ausführung von der Komponente zeichn getan werden sollte. Ansicht https://www.bennadel.com/blog/3184-creating-leaky-abstractions-with-rxjs-in-angular-2-1-1.htm für mehr.

//declare observable to listen to 
    private dataObservable: Observable<any>; 

    getData(slug: string): Observable<any> { 

    //If observable does not exist/is not running create a new one 
    if (!this.dataObservable) { 

     let stream = this.http.get(slug + "/api/Endpoint") 
      .map(this.extractData) 
      .finally(() => { 
       //Clear the observable now that it has been listened to 
       this.staffDataObservable = null; 
      }); 

     //Executes the http request immediately 
     this.dataObservable = Observable.fromPromise(stream.toPromise()); 

    }   

    return this.staffDataObservable; 
} 
Verwandte Themen