Datos de respuesta HTTP en caché utilizando Rxjs Observer / Observable + Caching + Subscription
Ver código abajo
* descargo de responsabilidad: soy nuevo en rxjs, así que tenga en cuenta que podría estar haciendo un mal uso del enfoque observable / observador. Mi solución es puramente un conglomerado de otras soluciones que encontré, y es la consecuencia de no haber encontrado una solución simple y bien documentada. Por lo tanto, estoy proporcionando mi solución de código completa (como me hubiera gustado encontrar) con la esperanza de que ayude a otros.
* nota, este enfoque se basa libremente en GoogleFirebaseObservables. Desafortunadamente, me falta la experiencia / tiempo adecuado para replicar lo que hicieron bajo el capó. Pero la siguiente es una forma simplista de proporcionar acceso asíncrono a algunos datos aptos para caché.
Situación : el componente 'lista de productos' tiene la tarea de mostrar una lista de productos. El sitio es una aplicación web de una sola página con algunos botones de menú que 'filtrarán' los productos que se muestran en la página.
Solución : El componente "se suscribe" a un método de servicio. El método de servicio devuelve una matriz de objetos de producto, a los que accede el componente a través de la devolución de llamada de suscripción. El método de servicio envuelve su actividad en un observador recién creado y devuelve el observador. Dentro de este observador, busca datos almacenados en caché y los devuelve al suscriptor (el componente) y los devuelve. De lo contrario, emite una llamada http para recuperar los datos, se suscribe a la respuesta, donde puede procesar esos datos (por ejemplo, asignar los datos a su propio modelo) y luego pasar los datos al suscriptor.
El código
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 (el modelo)
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...
);
}
}
Aquí hay una muestra de la salida que veo cuando cargo la página en Chrome. Tenga en cuenta que en la carga inicial, los productos se obtienen de http (llamar al servicio de reposo de mi nodo, que se ejecuta localmente en el puerto 3000). Cuando hago clic para navegar a una vista 'filtrada' de los productos, los productos se encuentran en caché.
Mi registro de Chrome (consola):
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
... [hizo clic en un botón de menú para filtrar los productos] ...
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
Conclusión: esta es la forma más simple que he encontrado (hasta ahora) para implementar datos de respuesta http almacenables en caché. En mi aplicación angular, cada vez que navego a una vista diferente de los productos, el componente de la lista de productos se recarga. ProductService parece ser una instancia compartida, por lo que la memoria caché local de 'productos: Producto []' en ProductService se retiene durante la navegación, y las llamadas posteriores a "GetProducts ()" devuelve el valor almacenado en caché. Una nota final, he leído comentarios sobre cómo deben cerrarse los observables / suscripciones cuando haya terminado para evitar 'pérdidas de memoria'. No he incluido esto aquí, pero es algo a tener en cuenta.