Designing Reactive Angular Components (RxJS)
@deborahkurata
Deborah Kurata
Developer
Pluralsight Author
Angular Getting Started
Angular Reactive Forms
Angular Routing
RxJS in Angular: Reactive Development
Angular NgRx: Getting Started
C# OOP & Best Practices
Microsoft Most Valuable Professional (MVP)
Google Developer Expert (GDE)
@deborahkurata
What Do We Mean: Reactive Components?
@deborahkurata
Shopping Cart
We want to react to changes
@deborahkurata
What Is Reactive Programming?
A declarative programming paradigm concerned with �data streams and the propagation of change.
https://en.wikipedia.org/wiki/Reactive_programming
@deborahkurata
Reactive Programming
Code is reactive when an �input change leads to an �automatic change in output
@deborahkurata
Benefits of Reactive Approach
Manage application state
React to user actions
Communicate between components
Compose streams of data
@deborahkurata
How do you design for reactivity?
@deborahkurata
Designing for Reactivity
Consider data flows
Validate the design
Consider reaction and propagation of change
@deborahkurata
Consider �data flows
@deborahkurata
Consider Data Flows
@deborahkurata
Consider
reaction and �propagation of change
@deborahkurata
Consider Reaction + Propagation of Change
@deborahkurata
Examples
@deborahkurata
Vehicle List Component: Data Flow
All available vehicles
Where will the data come from?
What will the data look like? Does it have everything we need?
@deborahkurata
Vehicle List Component: Data Flow
Vehicle classifications
Where will the data come from?
What will the data look like? Does it have everything we need?
@deborahkurata
Vehicle List Component: Reactivity
Vehicle classifications
What happens when the user selects a classification?
@deborahkurata
Vehicle List Component: Reactivity
List filters to selection
How do we filter the list?
1) Notified of the selection
2) Get vehicles that match
User selects a classification
@deborahkurata
Notification: Subject/BehaviorSubject
private vClassSubject = new BehaviorSubject<string>('');
vehicleClass$ = this.vClassSubject.asObservable();
vehicleClassSelected(vehicleClass: string): void {
this.vClassSubject.next(vehicleClass);
}
How do we use the selected class to filter the list?
@deborahkurata
React + Propagate the Change
vehicles$ = combineLatest([
this.allVehicles$,
this.vehicleClass$
])
.pipe(
map(([vehicles, selectedClass]) =>
vehicles.filter(v =>
selectedClass ?� v.vehicle_class.includes(selectedClass) : true
)),
catchError(this.handleError)
);
When the vehicle class changes, the list of vehicles changes
To work with multiple streams, use combineLatest
TIP
@deborahkurata
Vehicle List Component: Reactivity
What happens when the user selects a vehicle?
@deborahkurata
Vehicle List Component: Reactivity
Highlight selection
User picks a product
Display detail
@deborahkurata
Vehicle List Component: Reactivity
How do we display detail?
1) Notified of the selection
2) Get vehicle detail + films
@deborahkurata
Notification: Subject/BehaviorSubject
private vSelectedSubject = new BehaviorSubject<string>('');
vehicleSelected$ = this.vSelectedSubject.asObservable();
vehicleSelected(vehicleName: string): void {
this.vSelectedSubject.next(vehicleName);
}
Would be better as Id
@deborahkurata
React + Propagate the Change
selectedVehicle$ = this.vehicleSelected$.pipe(
switchMap(vehicleName =>
vehicleName.length ?
this.http.get<VehicleResponse>(
`${this.url}?search=${vehicleName}`)
)
);
When the selection changes, �the Observable emits
The emitted item is used as a query parameter
To "pass" a parameter, �emit it in the notification
TIP
@deborahkurata
Vehicle Detail Component: Data Flow
Vehicle detail
Related films
Where will the data come from?
What will the data look like? Does it have everything we need?
@deborahkurata
Retrieve Related Data
vehicleFilms$ = this.selectedVehicle$.pipe(
filter(Boolean),
switchMap(vehicle =>
forkJoin(vehicle.films.map(link =>
this.http.get<Film>(link)))
)
);
To retrieve related data, �use a higher-order mapping operator (switchMap)
TIP
When the Observable emits, �get the related films
@deborahkurata
Cart Component: Data Flow
Where will the data come from?
What will the data look like? Does it have everything we need?
@deborahkurata
Cart Data
export interface Cart {
vehicles: Vehicle[]
}
How do we track the purchase quantity?
@deborahkurata
Cart Data
export interface CartItem {
vehicle: Vehicle;
quantity: number;
}
type ActionType = 'add' | 'update' | 'delete';
export interface Action<T> {
item: T;
action: ActionType;
}
export interface Cart {
cartItems: CartItem[]
}
State Management Operation
How will we add an item to the cart?
@deborahkurata
Vehicle Detail Component: Reactivity
React to action
How do we add to cart?
1) Notified of the action
2) Add the vehicle to the cart
@deborahkurata
Notification: Subject/BehaviorSubject
private itemSubject = new Subject<Action<CartItem>>();
itemAction$ = this.itemSubject.asObservable();
addToCart(vehicle: Vehicle): void {
this.itemSubject.next({
item: { vehicle, quantity: 1 },
action: 'add'
});
}
Emits Action<CartItem>
@deborahkurata
React + Propagate Change to the Cart
private itemSubject = new Subject<Action<CartItem>>();
itemAction$ = this.itemSubject.asObservable();
cartItems$ = this.itemAction$
.pipe(
scan((items, itemAction) => � this.modifyCart(items, itemAction), [] as CartItem[])
);
scan maintains the array of cart items
To maintain an array of data, use scan
TIP
@deborahkurata
React + Propagate Change to the Cart
private modifyCart(items: CartItem[], op: Action<CartItem>): CartItem[] {
if (op.action === 'add') {
return [...items, op.item];
} else if (op.action === 'update') {
return items.map(item =>
item.vehicle.name === op.item.vehicle.name ? op.item : item)
} else if (op.action === 'delete') {
return items.filter(item => item.vehicle.name !== op.item.vehicle.name);
}
return [...items];
}
Leverages the spread syntax
@deborahkurata
Propagate Change to the Cart
private modifyCart(items: CartItem[], op: Action<CartItem>): CartItem[] {
if (op.action === 'add') {
return [...items, op.item];
} else if (op.action === 'update') {
return items.map(item =>
item.vehicle.name === op.item.vehicle.name ? op.item : item)
} else if (op.action === 'delete') {
return items.filter(item => item.vehicle.name !== op.item.vehicle.name);
}
return [...items];
}
@deborahkurata
Propagate Change to the Cart
private modifyCart(items: CartItem[], op: Action<CartItem>): CartItem[] {
if (op.action === 'add') {
return [...items, op.item];
} else if (op.action === 'update') {
return items.map(item =>
item.vehicle.name === op.item.vehicle.name ? op.item : item)
} else if (op.action === 'delete') {
return items.filter(item => item.vehicle.name !== op.item.vehicle.name);
}
return [...items];
}
@deborahkurata
Cart Component: Reactivity
Remove from cart
Update quantity
React to change in cart
React to change in cart
How do we update the cart calculations?
@deborahkurata
React to Change in Cart
totalPrice$ = combineLatest([
this.subTotal$,
this.deliveryFee$,
this.tax$,
]).pipe(map(([st, d, t]) => st + d + t));
subTotal$ = this.cartItems$.pipe(
map(items => items.reduce((a, b) =>
a + (b.quantity * Number(b.vehicle.cost_in_credits)), 0)),
);
deliveryFee$ = this.subTotal$.pipe(
map((t) => (t < 100000 ? 999 : 0))
);
tax$ = this.subTotal$.pipe(
map((t) => Math.round(t * 10.75) / 100)
);
@deborahkurata
Validate the design
@deborahkurata
Degrees of Freedom
@deborahkurata
Vehicle List/Vehicle Detail Components
4 variables
@deborahkurata
Vehicle List/Vehicle Detail Components
2 equations
@deborahkurata
Degrees of Freedom
# variables - # equations = degrees of freedom
4 - 2 = 2 degrees of freedom
private vClassSubject = new BehaviorSubject<string>('');
vehicleClass$ = this.vClassSubject.asObservable();
private vSelectedSubject = new BehaviorSubject<string>('');
vehicleSelected$ = this.vSelectedSubject.asObservable();
@deborahkurata
Cart Total Component
5 variables
@deborahkurata
Cart Total Component
4 equations
@deborahkurata
Degrees of Freedom
# variables - # equations = degrees of freedom
5 - 4 = 1 degree of freedom
private itemSubject = new Subject<Action<CartItem>>();
itemAction$ = this.itemSubject.asObservable();
@deborahkurata
Thank you!
@deborahkurata
https://github.com/DeborahK/� Angular-ReactiveDevelopment
https://bit.ly/rxjs-youtube
@deborahkurata