Données de réponse HTTP pouvant être mises en cache à l'aide de Rxjs Observer / Observable + Caching + Subscription
Voir le code ci-dessous
* Avertissement: Je suis nouveau sur rxjs, alors gardez à l'esprit que je peux mal utiliser l'approche observable / observateur. Ma solution est purement un conglomérat d'autres solutions que j'ai trouvées, et est la conséquence d'avoir échoué à trouver une solution simple et bien documentée. Ainsi, je fournis ma solution de code complète (comme j'aurais aimé la trouver) dans l'espoir qu'elle aide les autres.
* notez que cette approche est vaguement basée sur GoogleFirebaseObservables. Malheureusement, je manque d'expérience / de temps pour reproduire ce qu'ils ont fait sous le capot. Mais ce qui suit est un moyen simpliste de fournir un accès asynchrone à certaines données pouvant être mises en cache.
Situation : un composant «liste de produits» est chargé d'afficher une liste de produits. Le site est une application Web d'une seule page avec quelques boutons de menu qui «filtreront» les produits affichés sur la page.
Solution : le composant "s'abonne" à une méthode de service. La méthode de service renvoie un tableau d'objets de produit, auquel le composant accède via le rappel d'abonnement. La méthode de service encapsule son activité dans un Observateur nouvellement créé et renvoie l'observateur. À l'intérieur de cet observateur, il recherche les données mises en cache et les retransmet à l'abonné (le composant) et retourne. Sinon, il émet un appel http pour récupérer les données, s'abonne à la réponse, où vous pouvez traiter ces données (par exemple, mapper les données à votre propre modèle), puis transmettre les données à l'abonné.
Le 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 (le modèle)
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...
);
}
}
Voici un exemple de la sortie que je vois lorsque je charge la page dans Chrome. Notez qu'à la charge initiale, les produits sont récupérés depuis http (appel à mon service de repos de noeud, qui s'exécute localement sur le port 3000). Lorsque je clique ensuite pour accéder à une vue «filtrée» des produits, les produits sont trouvés dans le cache.
Mon journal Chrome (console):
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
... [cliqué sur un bouton de menu pour filtrer les produits] ...
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
Conclusion: C'est la manière la plus simple que j'ai trouvée (jusqu'à présent) pour implémenter des données de réponse http pouvant être mises en cache. Dans mon application angulaire, chaque fois que je navigue vers une vue différente des produits, le composant de la liste de produits se recharge. ProductService semble être une instance partagée, donc le cache local de «products: Product []» dans ProductService est conservé pendant la navigation, et les appels ultérieurs à «GetProducts ()» renvoient la valeur mise en cache. Une dernière note, j'ai lu des commentaires sur la façon dont les observables / abonnements doivent être fermés lorsque vous avez terminé pour éviter les «fuites de mémoire». Je ne l'ai pas inclus ici, mais c'est quelque chose à garder à l'esprit.