Reactive Streams in Angular

$ whoami

  • Engineering manager at Amazon
  • Organizer of Droidcon Boston (and maybe Seattle)
  • Organizer of SwiftFest Boston (and Seattle)
  • Meetups and community enthusiast

@giorgionatili

Agenda

  • Application layers
  • Application model in a nutshell
  • Application state
  • Reactive data streams
  • Demo

Ingredients

  • TypeScript 3.x
  • Angular 7.x
  • rxjs 6.x
  • ngrx 6.x
  • ngrx-forms 3.x

ReactiveX

App Layers

Models

State

Components

├── actions
│   └── user.action.ts
├── app.component.css
├── app.component.html
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── components
│   ├── create
│   └── index
├── models
│   └── User.ts
└── state
    └── user.state.ts

@giorgionatili

Services

Streams

Models

Value Objects

  • Abstraction to represent a complex type
  • Distinguishable only by the state of its properties
  • Comparable on the basis of their collective state
  • In most cases, immutable

@giorgionatili

export interface Person {
  firstName: string;
  lastName: string;
  age: number;
}

Entities

  • Similar to a value object
  • Distinguishable by properties and unique identifier
  • In most cases, immutable

@giorgionatili

export interface User {
  id: string;
  role: Roles;
}

enum Roles {
  Admin,
  RegisteredUser,
  AnonymousUser
}

Aggregates

  • Draw a boundary around one or more entities
  • Describe a self-contained part of the app domain
  • Editable through services (with rules)
  • In most cases, immutable

@giorgionatili

export interface Group {

  id: number;
  name: string;
  users: User[];
  permissions: Roles[];
}

Immutability

App State

Store

  • The store is a global state manager
  • It dispatches actions that state containers listen to
  • It exposes data slices out from the global state

@giorgionatili

import { Store } from '@ngrx/store';
import { AddPerson } from './person.actions';
​
@Component({ ... })
export class AddPersonComponent {
  constructor(private store: Store) {}
​
  addPerson(name: string) {
    this.store.dispatch(new AddPerson(name));
  }
}

Actions

  • Actions can be thought as commands or events
  • Each action contains a type field which is the unique identifier
  • Actions can contain data
  • Actions can be synchronous and asynchronous

@giorgionatili

export class AddPerson {

  static readonly type = '[Person] Add Person';
  constructor(public name: string) {}
}

State

  • States are classes along with decorators to describe metadata and action mappings
  • States are classes that give you a context for modeling, selecting, and handling changes of data.

@giorgionatili

import { State } from '@ngxs/store';
​
@State<string[]>({
  name: 'users',
  defaults: []
})
export class UsersState {}

Selects

  • Selects are a representation of Observable slices of the app state

  • The @Select decorator allows selecting slices of data from the store

  • The slices of data can be acquired passing a state class or a function

@giorgionatili

import { Select } from '@ngxs/store';

@Component({ ... })
export class GroupComponent {

  // Reads the name of the state from the state class
  @Select(GroupState) people$: Observable<string[]>;
​
}

Reducers

  • Reducers specify how the application's state changes in response to actions sent to the store
  • Actions only describe what happened, but don't describe how the application's state changes
  • Reducers are pure function that concur to the manipulation of the app state

@giorgionatili

Data Streams

Reactive forms

  • A model-driven approach to handling inputs
  • Sophisticated validation
  • More readable than template driven forms
  • Encourage decoupling the logic from the presentation

@giorgionatili

@Component({
  selector: 'app-profile-editor',
  template: `
    <form [formGroup]="profileForm">
      <label>
        First Name: <input type="text" formControlName="firstName">
      </label>
      <label>
        Last Name: <input type="text" formControlName="lastName">
      </label>
    </form>
  `,
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
}

Observables

  • Observer subscribes to an Observable
  • Observer reacts to whatever value Observable emits
  • Think of the Observable as a “push” equivalent to Iterable

@giorgionatili

import { from } from 'rxjs';

// Emit array as a sequence of values
const arraySource = from([1, 2, 3, 4, 5]);

const subscribe = arraySource
    .subscribe(val => console.log(val));

Rx Operators

  • ReactiveX provides a wide collection of operators
  • Operators can filter, select, transform, combine, and compose Observables
  • Most operators operate on an Observable and return an Observable

@giorgionatili

https://www.learnrxjs.io/operators/

import { from } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';

const myArrayWithDuplicatesInARow = from([1, 1, 2, 2, 3, 1, 2, 3]);

const distinctSub = myArrayWithDuplicatesInARow
  .pipe(distinctUntilChanged())
  .subscribe(val => console.log('DISTINCT SUB:', val));

// Output: DISTINCT SUB: 1,2,3,1,2,3

Pipe

  • You pull in any operator you need from  'rxjs/operators'
  • You concatenate the operators
  • Each operator is a pure function

@giorgionatili

Demo

Q&A

Useful links

  • https://ngxs.gitbook.io/ngxs/advanced/meta-reducer
  • https://github.com/ngxs/store/issues/139#issuecomment-390673126
  • https://angularfirebase.com/lessons/ngxs-quick-start-angular-state-management/
  • https://ngxs.gitbook.io/ngxs/concepts/select

Thanks!

Keep calm and streakm on...