Dados de resposta HTTP em cache usando Rxjs Observer / Observable + Caching + Subscription
Veja o código abaixo
* aviso de isenção de responsabilidade: sou novo no rxjs, portanto, lembre-se de que posso estar abusando da abordagem observável / observador. Minha solução é puramente um conglomerado de outras soluções que encontrei e é a conseqüência de não conseguir encontrar uma solução simples e bem documentada. Portanto, estou fornecendo minha solução completa de código (como gostaria de ter encontrado) na esperança de que ajude outras pessoas.
* observe que essa abordagem se baseia livremente no GoogleFirebaseObservables. Infelizmente, não tenho a experiência / tempo adequados para replicar o que eles fizeram sob o capô. Mas a seguir, é uma maneira simplista de fornecer acesso assíncrono a alguns dados capazes de cache.
Situação : Um componente 'lista de produtos' é encarregado de exibir uma lista de produtos. O site é um aplicativo da web de página única com alguns botões de menu que filtram os produtos exibidos na página.
Solução : O componente "assina" um método de serviço. O método de serviço retorna uma matriz de objetos do produto, que o componente acessa através do retorno de chamada da assinatura. O método de serviço agrupa sua atividade em um Observador recém-criado e retorna o observador. Dentro desse observador, ele procura dados em cache e os repassa para o assinante (o componente) e retorna. Caso contrário, emite uma chamada http para recuperar os dados, assina a resposta, onde você pode processar esses dados (por exemplo, mapear os dados para o seu próprio modelo) e depois repassá-los ao assinante.
O 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 (o 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...
);
}
}
Aqui está um exemplo da saída que vejo quando carrego a página no Chrome. Observe que, no carregamento inicial, os produtos são buscados a partir de http (chame meu serviço de descanso de nó, que está sendo executado localmente na porta 3000). Quando clico para navegar para uma exibição 'filtrada' dos produtos, eles são encontrados no cache.
Meu registro do 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
... [clicou em um botão de menu para filtrar os produtos] ...
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
Conclusão: Essa é a maneira mais simples que eu encontrei (até agora) de implementar dados de resposta http em cache. No meu aplicativo angular, cada vez que navego para uma visualização diferente dos produtos, o componente da lista de produtos é recarregado. O ProductService parece ser uma instância compartilhada; portanto, o cache local de 'products: Product []' no ProductService é mantido durante a navegação e as chamadas subseqüentes a "GetProducts ()" retornam o valor em cache. Uma observação final, eu li comentários sobre como as observáveis / assinaturas precisam ser fechadas quando você terminar para evitar 'vazamentos de memória'. Eu não incluí isso aqui, mas é algo a ter em mente.