Zwischenspeicherbare HTTP-Antwortdaten mit Rxjs Observer / Observable + Caching + Subscription
Siehe Code unten
* Haftungsausschluss: Ich bin neu bei rxjs. Denken Sie also daran, dass ich den Observable / Observer-Ansatz möglicherweise missbrauche. Meine Lösung ist lediglich ein Konglomerat anderer Lösungen, die ich gefunden habe, und ist die Folge davon, dass ich keine einfache, gut dokumentierte Lösung gefunden habe. Daher biete ich meine vollständige Codelösung (wie ich sie gerne gefunden hätte) in der Hoffnung an, dass sie anderen hilft.
* Beachten Sie, dass dieser Ansatz lose auf GoogleFirebaseObservables basiert. Leider fehlt mir die richtige Erfahrung / Zeit, um zu wiederholen, was sie unter der Haube getan haben. Das Folgende ist jedoch eine vereinfachte Methode, um einen asynchronen Zugriff auf einige cachefähige Daten bereitzustellen.
Situation : Eine 'Produktlisten'-Komponente hat die Aufgabe, eine Produktliste anzuzeigen. Die Website ist eine einseitige Web-App mit einigen Menüschaltflächen, mit denen die auf der Seite angezeigten Produkte "gefiltert" werden.
Lösung : Die Komponente "abonniert" eine Servicemethode. Die Servicemethode gibt ein Array von Produktobjekten zurück, auf die die Komponente über den Abonnement-Rückruf zugreift. Die Servicemethode packt ihre Aktivität in einen neu erstellten Observer und gibt den Observer zurück. Innerhalb dieses Beobachters sucht er nach zwischengespeicherten Daten und gibt sie an den Abonnenten (die Komponente) zurück und gibt sie zurück. Andernfalls wird ein http-Aufruf zum Abrufen der Daten ausgegeben, die Antwort abonniert, in der Sie diese Daten verarbeiten (z. B. die Daten Ihrem eigenen Modell zuordnen) und die Daten dann an den Abonnenten zurückgeben können.
Der Code
product-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ProductService } from '../../../services/product.service';
import { Product, ProductResponse } from '../../../models/Product';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products: Product[];
constructor(
private productService: ProductService
) { }
ngOnInit() {
console.log('product-list init...');
this.productService.getProducts().subscribe(products => {
console.log('product-list received updated products');
this.products = products;
});
}
}
product.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable, Observer } from 'rxjs';
import 'rxjs/add/operator/map';
import { Product, ProductResponse } from '../models/Product';
@Injectable()
export class ProductService {
products: Product[];
constructor(
private http:Http
) {
console.log('product service init. calling http to get products...');
}
getProducts():Observable<Product[]>{
//wrap getProducts around an Observable to make it async.
let productsObservable$ = Observable.create((observer: Observer<Product[]>) => {
//return products if it was previously fetched
if(this.products){
console.log('## returning existing products');
observer.next(this.products);
return observer.complete();
}
//Fetch products from REST API
console.log('** products do not yet exist; fetching from rest api...');
let headers = new Headers();
this.http.get('http://localhost:3000/products/', {headers: headers})
.map(res => res.json()).subscribe((response:ProductResponse) => {
console.log('productResponse: ', response);
let productlist = Product.fromJsonList(response.products); //convert service observable to product[]
this.products = productlist;
observer.next(productlist);
});
});
return productsObservable$;
}
}
product.ts (das Modell)
export interface ProductResponse {
success: boolean;
msg: string;
products: Product[];
}
export class Product {
product_id: number;
sku: string;
product_title: string;
..etc...
constructor(product_id: number,
sku: string,
product_title: string,
...etc...
){
//typescript will not autoassign the formal parameters to related properties for exported classes.
this.product_id = product_id;
this.sku = sku;
this.product_title = product_title;
...etc...
}
//Class method to convert products within http response to pure array of Product objects.
//Caller: product.service:getProducts()
static fromJsonList(products:any): Product[] {
let mappedArray = products.map(Product.fromJson);
return mappedArray;
}
//add more parameters depending on your database entries and constructor
static fromJson({
product_id,
sku,
product_title,
...etc...
}): Product {
return new Product(
product_id,
sku,
product_title,
...etc...
);
}
}
Hier ist ein Beispiel für die Ausgabe, die ich beim Laden der Seite in Chrome sehe. Beachten Sie, dass beim ersten Laden die Produkte von http abgerufen werden (Aufruf meines Node Rest-Dienstes, der lokal auf Port 3000 ausgeführt wird). Wenn ich dann auf klicke, um zu einer gefilterten Ansicht der Produkte zu navigieren, werden die Produkte im Cache gefunden.
Mein Chrome-Protokoll (Konsole):
core.es5.js:2925 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:19 app.component url: /products
product.service.ts:15 product service init. calling http to get products...
product-list.component.ts:18 product-list init...
product.service.ts:29 ** products do not yet exist; fetching from rest api...
product.service.ts:33 productResponse: {success: true, msg: "Products found", products: Array(23)}
product-list.component.ts:20 product-list received updated products
... [klickte auf eine Menüschaltfläche, um die Produkte zu filtern] ...
app.component.ts:19 app.component url: /products/chocolatechip
product-list.component.ts:18 product-list init...
product.service.ts:24 ## returning existing products
product-list.component.ts:20 product-list received updated products
Fazit: Dies ist der einfachste Weg, den ich (bisher) gefunden habe, um zwischengespeicherte http-Antwortdaten zu implementieren. In meiner eckigen App wird die Produktlistenkomponente jedes Mal neu geladen, wenn ich zu einer anderen Ansicht der Produkte navigiere. ProductService scheint eine gemeinsam genutzte Instanz zu sein, daher bleibt der lokale Cache von 'products: Product []' im ProductService während der Navigation erhalten, und nachfolgende Aufrufe von "GetProducts ()" geben den zwischengespeicherten Wert zurück. Ein letzter Hinweis: Ich habe Kommentare darüber gelesen, wie Observables / Abonnements geschlossen werden müssen, wenn Sie fertig sind, um Speicherverluste zu vermeiden. Ich habe dies hier nicht aufgenommen, aber es ist etwas zu beachten.