A data model gives an application a consistent way to represent its data and perform operations on it. In a to-do application, a TodoItem holds an ID, a task, and a completion flag; a TodoCollection holds items and provides methods to add, find, and mark them complete. section 2.2.5
Listing 2.12 The contents of the todoCollection.ts file in the src folder
import { TodoItem } from "./todoItem";
export class TodoCollection {
private nextId: number = 1;
constructor(public userName: string,
public todoItems: TodoItem[] = []) {
// no statements required
}
addTodo(task: string): number {
while (this.getTodoById(this.nextId)) {
this.nextId++;
}
this.todoItems.push(new TodoItem(this.nextId, task));
return this.nextId;
}
getTodoById(id: number) : TodoItem {
return this.todoItems.find(item => item.id === id);
}
markComplete(id: number, complete: boolean) {
const todoItem = this.getTodoById(id);
if (todoItem) {
todoItem.complete = complete;
}
}
}
The todoCollection.ts listing shows how a collection stores typed items and puts operations alongside the data they change. section 2.2.5
Choose a type alias when the model needs a shape for related properties, and a class when it also needs behavior. A product is described by fields such as ID, name, and price. An order line pairs a product with a quantity and calculates its total; an order stores lines in a map keyed by product ID. section 16.5
Listing 16.15 The contents of the entities.ts file in the src/data folder
export type Product = {
id: number,
name: string,
description: string,
category: string,
price: number
};
export class OrderLine {
constructor(public product: Product, public quantity: number) {
// no statements required
}
get total(): number {
return this.product.price * this.quantity;
}
}
export class Order {
private lines = new Map<number, OrderLine>();
constructor(initialLines?: OrderLine[]) {
if (initialLines) {
initialLines.forEach(ol => this.lines.set(ol.product.id, ol));
}
}
public addProduct(prod: Product, quantity: number) {
if (this.lines.has(prod.id)) {
if (quantity === 0) {
this.removeProduct(prod.id);
} else {
this.lines.get(prod.id)!.quantity += quantity;
}
} else {
this.lines.set(prod.id, new OrderLine(prod, quantity));
}
}
public removeProduct(id: number) {
this.lines.delete(id);
}
get orderLines(): OrderLine[] {
return [...this.lines.values()];
}
get productCount(): number {
return [...this.lines.values()]
.reduce((total, ol) => total += ol.quantity, 0);
}
get total(): number {
return [...this.lines.values()]
.reduce((total, ol) => total += ol.total, 0);
}
}
The entities.ts listing puts the Product type alias beside the OrderLine and Order classes, making the distinction between data shape and behavior concrete. section 16.5
Catch a mismatched operation
addTodo expects a task string. Passing it a TodoItem instead produces a compiler error: the argument is not assignable to string. The annotations on the model make that mistake visible before execution. section 2.2.5
The same Product, OrderLine, and Order definitions can be placed in an Angular application's src/app/data/entities.ts without changing the entity code: Angular uses regular TypeScript classes for its data model. section 18.3






















