Dane odpowiedzi HTTP w pamięci podręcznej przy użyciu Rxjs Observer / Observable + Caching + Subskrypcja
Zobacz kod poniżej
* zastrzeżenie: Jestem nowy w rxjs, więc pamiętaj, że mogę niewłaściwie używać podejścia obserwowalnego / obserwatora. Moje rozwiązanie jest wyłącznie konglomeratem innych rozwiązań, które znalazłem, i jest konsekwencją tego, że nie znalazłem prostego, dobrze udokumentowanego rozwiązania. W ten sposób dostarczam moje kompletne rozwiązanie kodu (jak chciałbym to znaleźć) w nadziei, że pomoże innym.
* Uwaga, to podejście jest luźno oparte na GoogleFirebaseObservables. Niestety brakuje mi odpowiedniego doświadczenia / czasu, aby powtórzyć to, co zrobili pod maską. Ale poniżej przedstawiono uproszczony sposób zapewnienia asynchronicznego dostępu do niektórych danych, które mogą być buforowane.
Sytuacja : Zadaniem komponentu „lista produktów” jest wyświetlanie listy produktów. Witryna to jednostronna aplikacja internetowa z kilkoma przyciskami menu, które będą „filtrować” produkty wyświetlane na stronie.
Rozwiązanie : składnik „subskrybuje” metodę usługi. Metoda usługi zwraca tablicę obiektów produktu, do których komponent uzyskuje dostęp poprzez wywołanie zwrotne subskrypcji. Metoda usługi zawija swoją aktywność w nowo utworzonym Observer i zwraca obserwatora. Wewnątrz tego obserwatora wyszukuje dane w pamięci podręcznej i przekazuje je subskrybentowi (komponentowi) i zwraca. W przeciwnym razie wywołuje połączenie http w celu pobrania danych, subskrybuje odpowiedź, w której można przetworzyć te dane (np. Zmapować dane do własnego modelu), a następnie przekazać dane subskrybentowi.
Kod
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 (model)
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...
);
}
}
Oto próbka danych wyjściowych, które widzę, gdy ładuję stronę w Chrome. Zauważ, że przy początkowym ładowaniu produkty są pobierane z http (połączenie z moją usługą odpoczynku węzła, która działa lokalnie na porcie 3000). Kiedy następnie klikam, aby przejść do widoku „filtrowanego” produktów, produkty znajdują się w pamięci podręcznej.
Mój dziennik Chrome (konsola):
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
... [kliknął przycisk menu, aby przefiltrować produkty] ...
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
Wniosek: Jest to najprostszy (jak dotąd) sposób zaimplementowania buforowanych danych odpowiedzi HTTP. W mojej aplikacji kątowej za każdym razem, gdy przechodzę do innego widoku produktów, składnik listy produktów ładuje się ponownie. ProductService wydaje się być współużytkowanym wystąpieniem, więc lokalna pamięć podręczna „products: Product []” w ProductService jest zachowywana podczas nawigacji, a kolejne wywołania „GetProducts ()” zwracają buforowaną wartość. I ostatnia uwaga, przeczytałem komentarze o tym, jak obserwowalne / subskrypcje muszą być zamknięte, kiedy skończysz, aby zapobiec „wyciekom pamięci”. Nie zamieściłem tego tutaj, ale warto o tym pamiętać.