Dati di risposta HTTP memorizzabili nella cache utilizzando Rxjs Observer / Observable + Caching + Abbonamento
Vedi il codice sotto
* disclaimer: sono un nuovo utente di rxjs, quindi tieni presente che potrei usare impropriamente l'approccio osservabile / osservatore. La mia soluzione è puramente una conglomerazione di altre soluzioni che ho trovato ed è la conseguenza di non aver trovato una soluzione semplice e ben documentata. Quindi sto fornendo la mia soluzione di codice completa (come mi sarebbe piaciuto aver trovato) nella speranza che aiuti gli altri.
* nota, questo approccio è vagamente basato su GoogleFirebaseObservables. Purtroppo mi manca la giusta esperienza / tempo per replicare ciò che hanno fatto sotto il cofano. Ma il seguente è un modo semplicistico di fornire accesso asincrono ad alcuni dati in grado di memorizzare nella cache.
Situazione : un componente "elenco prodotti" ha il compito di visualizzare un elenco di prodotti. Il sito è un'app Web a pagina singola con alcuni pulsanti di menu che "filtrano" i prodotti visualizzati nella pagina.
Soluzione : il componente "sottoscrive" a un metodo di servizio. Il metodo di servizio restituisce una matrice di oggetti prodotto a cui il componente accede tramite il callback dell'abbonamento. Il metodo di servizio avvolge la sua attività in un osservatore appena creato e restituisce l'osservatore. All'interno di questo osservatore, cerca i dati memorizzati nella cache e li restituisce al sottoscrittore (il componente) e ritorna. Altrimenti emette una chiamata http per recuperare i dati, si abbona alla risposta, dove è possibile elaborare tali dati (ad es. Mappare i dati sul proprio modello) e quindi restituire i dati al sottoscrittore.
Il codice
prodotto-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 (il modello)
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...
);
}
}
Ecco un esempio dell'output che vedo quando carico la pagina in Chrome. Si noti che al caricamento iniziale, i prodotti vengono recuperati da http (chiamare il mio servizio di resto del nodo, che è in esecuzione localmente sulla porta 3000). Quando faccio clic su per passare a una vista "filtrata" dei prodotti, i prodotti vengono trovati nella cache.
Il mio Chrome Log (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
... [fatto clic su un pulsante del menu per filtrare i prodotti] ...
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
Conclusione: questo è il modo più semplice che ho trovato (finora) per implementare i dati di risposta http memorizzabili nella cache. Nella mia app angolare, ogni volta che passo a una diversa visualizzazione dei prodotti, il componente dell'elenco prodotti viene ricaricato. ProductService sembra essere un'istanza condivisa, quindi la cache locale di 'prodotti: Product []' in ProductService viene mantenuta durante la navigazione e le successive chiamate a "GetProducts ()" restituiscono il valore memorizzato nella cache. Un'ultima nota, ho letto i commenti su come osservabili / abbonamenti devono essere chiusi quando hai finito per prevenire "perdite di memoria". Non l'ho incluso qui, ma è qualcosa da tenere a mente.