1 of 47

Designing Reactive Angular Components (RxJS)

@deborahkurata

2 of 47

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

3 of 47

What Do We Mean: Reactive Components?

@deborahkurata

4 of 47

Shopping Cart

We want to react to changes

@deborahkurata

5 of 47

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

6 of 47

Reactive Programming

Code is reactive when an �input change leads to an �automatic change in output

@deborahkurata

7 of 47

Benefits of Reactive Approach

Manage application state

React to user actions

Communicate between components

Compose streams of data

@deborahkurata

8 of 47

How do you design for reactivity?

@deborahkurata

9 of 47

Designing for Reactivity

Consider data flows

Validate the design

Consider reaction and propagation of change

@deborahkurata

10 of 47

Consider �data flows

@deborahkurata

11 of 47

Consider Data Flows

  • Where does the data come from?
    • User entry?
    • An http request? (Does it return as we want?)
    • A calculation?
  • Where does the data go?
    • To another component?
    • To a backend server?

@deborahkurata

12 of 47

Consider

reaction andpropagation of change

@deborahkurata

13 of 47

Consider Reaction + Propagation of Change

  • What is the source of change?
    • User action?
    • An http response?
  • How should the app react?
    • Issue an http request?
    • Recalculate?
    • Redisplay?

@deborahkurata

14 of 47

Examples

@deborahkurata

15 of 47

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

16 of 47

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

17 of 47

Vehicle List Component: Reactivity

Vehicle classifications

What happens when the user selects a classification?

@deborahkurata

18 of 47

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

19 of 47

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

20 of 47

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

21 of 47

Vehicle List Component: Reactivity

What happens when the user selects a vehicle?

@deborahkurata

22 of 47

Vehicle List Component: Reactivity

Highlight selection

User picks a product

Display detail

@deborahkurata

23 of 47

Vehicle List Component: Reactivity

How do we display detail?

1) Notified of the selection

2) Get vehicle detail + films

@deborahkurata

24 of 47

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

25 of 47

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

26 of 47

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

27 of 47

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

28 of 47

Cart Component: Data Flow

Where will the data come from?

What will the data look like? Does it have everything we need?

@deborahkurata

29 of 47

Cart Data

export interface Cart {

vehicles: Vehicle[]

}

How do we track the purchase quantity?

@deborahkurata

30 of 47

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

31 of 47

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

32 of 47

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

33 of 47

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

34 of 47

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

35 of 47

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

36 of 47

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

37 of 47

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

38 of 47

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

39 of 47

Validate the design

@deborahkurata

40 of 47

Degrees of Freedom

  • Component has
    • State: Interdependent variables
    • Equations: Express variable relationships
  • Degrees of Freedom
    • The number of variables that fully describe a component's state
    • Number of variables - number of equations
    • Often represent what the user can change

@deborahkurata

41 of 47

Vehicle List/Vehicle Detail Components

  • vehicleClasses$
    • Invariant
  • selectedClass$
    • Variable
  • vehicles$
    • Variable
  • selectedVehicle$
    • Variable
  • vehicleFilms$
    • Variable

4 variables

@deborahkurata

42 of 47

Vehicle List/Vehicle Detail Components

  • vehicles$
    • Based on selected class
  • vehicleFilms$
    • Based on selected vehicle

2 equations

@deborahkurata

43 of 47

Degrees of Freedom

# variables - # equations = degrees of freedom

4 - 2 = 2 degrees of freedom

  • Set (and react to) 2 degrees of freedom
    • Changes in the selected vehicle classification
    • Changes in the selected vehicle

private vClassSubject = new BehaviorSubject<string>('');

vehicleClass$ = this.vClassSubject.asObservable();

private vSelectedSubject = new BehaviorSubject<string>('');

vehicleSelected$ = this.vSelectedSubject.asObservable();

@deborahkurata

44 of 47

Cart Total Component

  • cartItems$
    • Variable
  • subTotal$
    • Variable
  • deliveryFee$
    • Variable
  • tax$
    • Variable
  • totalPrice$
    • Variable

5 variables

@deborahkurata

45 of 47

Cart Total Component

  • subTotal$
    • Based on cartItems$
  • deliveryFee$
    • Based on subTotal$
  • tax$
    • Based on subTotal$
  • totalPrice$
    • Based on subTotal$, deliveryFee$, tax$

4 equations

@deborahkurata

46 of 47

Degrees of Freedom

# variables - # equations = degrees of freedom

5 - 4 = 1 degree of freedom

  • Set (and react to) 1 degree of freedom
    • Changes to the cart

private itemSubject = new Subject<Action<CartItem>>();

itemAction$ = this.itemSubject.asObservable();

@deborahkurata

47 of 47

Thank you!

@deborahkurata

https://github.com/DeborahK/� Angular-ReactiveDevelopment

https://bit.ly/rxjs-youtube

@deborahkurata