A data source gives a web application one interface for obtaining products while allowing the products to come from local test data or a web service. In a stand-alone TypeScript application, AbstractDataSource holds products, categories, and an order. Its constructor starts loading; getProducts and getCategories wait for that work before returning results. section 16.5.1
Listing 16.16 The contents of the abstractdatasource.ts file in the src/data folder
import { Product, Order } from "./entities";
export type ProductProp = keyof Product;
export abstract class AbstractDataSource {
private _products: Product[];
private _categories: Set<string>;
public order: Order;
public loading: Promise<void>;
constructor() {
this._products = [];
this._categories = new Set<string>();
this.order = new Order();
this.loading = this.getData();
}
async getProducts(sortProp: ProductProp = "id",
category? : string): Promise<Product[]> {
await this.loading;
return this.selectProducts(this._products, sortProp, category);
}
protected async getData(): Promise<void> {
this._products = [];
this._categories.clear();
const rawData = await this.loadProducts();
rawData.forEach(p => {
this._products.push(p);
this._categories.add(p.category);
});
}
protected selectProducts(prods: Product[],
sortProp: ProductProp, category?: string): Product[] {
return prods.filter(p=> category === undefined
|| p.category === category)
.sort((p1, p2) => p1[sortProp] < p2[sortProp]
? -1 : p1[sortProp] > p2[sortProp] ? 1: 0);
}
async getCategories(): Promise<string[]> {
await this.loading;
return [...this._categories.values()];
}
protected abstract loadProducts(): Promise<Product[]>;
abstract storeOrder(): Promise<number>;
}
The src/data/abstractDataSource.ts listing shows the shared state, the loading Promise, and the asynchronous product query. section 16.5.1
LocalDataSource implements loadProducts with Promise.resolve and a small product array. RemoteDataSource implements the same operation with an Axios GET request; it also sends order details to the service with POST. Starting locally lets the application take shape before HTTP requests are added. section 16.5.1 section 17.2
Using the source in a display
In src/index.ts, create a LocalDataSource and await getProducts("name"), getCategories(), or getProducts("name", "Chess"). For the browser display, pass that same instance as HtmlDisplay’s dataSource prop. HtmlDisplay queries products using its selected category and replaces its rendered HTML when a callback changes the selection. This is a simple update strategy, not an attempt to minimize browser work. section 16.5.1 section 16.8.2
An Angular version separates DataSource from an abstract DataSourceImpl. Angular injects the configured implementation into DataSource. Here, loadProducts returns an RxJS Observable<Product[]> rather than a Promise; subscribing receives product arrays as they arrive, and Angular handles display updates. section 18.3.1
Listing 18.10 The contents of the dataSource.ts file in the src/app/data folder
import { Observable } from "rxjs";
import { Injectable } from '@angular/core';
import { Product, Order } from "./entities";
export type ProductProp = keyof Product;
export abstract class DataSourceImpl {
abstract loadProducts(): Observable<Product[]>;
abstract storeOrder(order: Order): Observable<number>;
}
@Injectable()
export class DataSource {
private _products: Product[];
private _categories: Set<string>;
public order: Order;
constructor(private impl: DataSourceImpl) {
this._products = [];
this._categories = new Set<string>();
this.order = new Order();
this.getData();
}
getProducts(sortProp: ProductProp = "id", category? : string)
: Product[] {
return this.selectProducts(this._products, sortProp, category);
}
protected getData(): void {
this._products = [];
this._categories.clear();
this.impl.loadProducts().subscribe(rawData => {
rawData.forEach(p => {
this._products.push(p);
this._categories.add(p.category);
});
});
}
protected selectProducts(prods: Product[], sortProp: ProductProp,
category?: string): Product[] {
return prods
.filter(p => category === undefined || p.category === category)
.sort((p1, p2) => p1[sortProp] < p2[sortProp]
? -1 : p1[sortProp] > p2[sortProp] ? 1: 0);
}
getCategories(): string[] {
return [...this._categories.values()];
}
storeOrder(): Observable<number> {
return this.impl.storeOrder(this.order);
}
}
The src/app/data/dataSource.ts listing shows the injectable Angular class, its abstract implementation dependency, and its Observable-based loading contract. section 18.3.1












