Observables in Angular

Why Observables

User demands complex systems

Reactive Manifesto
New systems have to be more flexible, loosely-coupled and scalable

Asynchronous nightmares

More complex web systems produce

  • memory leaks
  • race conditions
  • callback hell
  • complex state machines
  • error handling
  • ...

Promises to the rescue​

They became so important, they are part now of ES6

Just saying

Promises analysis


let promise = http.get('http://www.url.com/login');
// ... 
promise.then(processLogin, errorLogin)
    .then(getUserId, errorHandler)
    .then(getPortfolio, errorHandler)

Benefits

  • Readability - chain flows
  • Loosely-coupled - decoupling the execution of a task from implementation / restoring control back to the calling code  

Problems

  • No-cancellable / no-repeat:  the promise will solve or reject and only once
  • Error handling

Observables

Benefits

  • Also loosely coupled and easy to read
  • Streams - sets of values of any number of things over any amount of time
  • Lazy - observables will not generate values until subscribe
  • Cancellable

Resolves the same problem, different approach

Similar semantics


x.then(valueFn, errorFn)

x.subscribe(valueFn, errorFn, completeFn)

Promise: 

Observable: 


let sub = x.subscribe(valueFn, errorFn, completeFn)
// later time ...
sub.unsubscribe()

What are these Observables?

Observer pattern definition

The observer pattern is used to allow an object to publish changes to its state. Other objects subscribe to be immediately notified of any changes.

Design Patterns, GoF

Observables are functions that tie observers to a producer

Observers in JS

Object.observe feature is obsolete. Use Proxy instead.
Babel does not support Proxies (cannot be transpiled or polyfilled).

Just saying

Rx - Reactive eXtensions

Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences.

Operators are methods on Observables that allow you to compose new observables

  • filter
  • map
  • reduce
  • first
  • last
  • flatMap
  • switchMap
  • debounce

Basic example


const startButton = document.querySelector('.start');
const stopButton = document.querySelector('.stop');

const startButton$ = Rx.Observable.fromEvent(startButton, 'click');
const stopButton$ = Rx.Observable.fromEvent(stopButton, 'click');


let subscription = startButton$
  .switchMap(() => Rx.Observable.interval(1000) )
  .subscribe((x) => console.log(x) );

stopButton$
  .subscribe(() => subscription.unsubscribe() );
  <button class="start">Start</button>
  <button class="stop">Stop</button>

RxJS in Angular

  • Event Emitters
  • Forms & Http service
  • Observable Data service

Event Emitters

Event emitter 

EventEmitter extends Subject, but it may change.

EventEmitter should be used only for @Output()s in components and directives.

Angular is about components

@Component({
  selector: 'start-stop',
  template: `
      <button (click)="start()">Start</button>
      <button (click)="stop()">Stop</button>
   `
})
export class StartStopComponent {
  @Output() startEmitter: EventEmitter<any> = new EventEmitter();
  @Output() stopEmitter: EventEmitter<any> = new EventEmitter();
  
  start() {
    this.startEmitter.emit(null);
  }

  stop() {
    this.stopEmitter.emit(null);
  }
}
<start-stop (startEmitter)="onStart()" (stopEmitter)="onStop()"></start-stop>
child.component.ts
parent.component.ts

Demo

Reactive Forms

&

http service

 

Angular offers two types of forms

Template-base forms
 

 

 

Reactive forms
 


Use ngModel to create two-way data bindings for reading and writing input-control values

 


Rather than update the data model directly, the component extracts user changes and forwards them to an external component or service.

Template-base forms


<form>
    <label for="name">Name:</label>
    <input type="text" name="name" [(ngModel)]="model.name">

    <label for="address">Address:</label>
    <input type="text" name="address" [(ngModel)]="model.address">
</form>
import { FormsModule } from '@angular/forms';
app.module.ts
form.template.html

Reactive forms


<form [formGroup]="registerForm">
    <label for="name">Name:</label>
    <input type="text" name="name" formControlName="name">

    <label for="address">Address:</label>
    <input type="text" name="address" formControlName="address">
</form>  
import { ReactiveFormsModule } from '@angular/forms';
app.module.ts
register.template.html
import { FormGroup, FormControl } from '@angular/forms';


export class RegisterComponent {
  registerForm = new FormGroup ({
    name: new FormControl(),
    address: new FormControl()
  });
}
register.component.ts

Benefits of reactive forms


export class RegisterComponent implements OnInit {
    registerForm = new FormGroup ({
      name: new FormControl(),
      address: new FormControl()
    });

    ngOnInit() {
        let form$ = this.registerForm.valueChanges
    }
}

Calling valueChanges gets a RxJS Observable

register.component.ts

Subscribe to form changes


export class RegisterComponent implements OnInit {
    registerForm = new FormGroup ({
      name: new FormControl(),
      address: new FormControl()
    });

    ngOnInit() {
        this.registerForm.valueChanges
            .subscribe(form => console.log(form));
    }
}

Nothing happens until subscribe

register.component.ts

Buffer

// ...
import 'rxjs/add/operator/debounceTime';


export class RegisterComponent {
    // ...

    this.registerForm.valueChanges
        .debounceTime(500)
        .subscribe(term => console.log(term))
}

Only execute the last request by buffering the events and handle only the last after some time of inactivity.

register.component.ts

Avoid repeating

// ...
import 'rxjs/add/operator/distinctUntilChanged';


export class RegisterComponent {
    // ...

    this.registerForm.valueChanges
        .debounceTime(500)
        .distinctUntilChange()
        .subscribe(term => console.log(term))
}
register.component.ts

Only execute a request different from previous one.

Filter searches

// ...
import 'rxjs/add/operator/filter';


export class RegisterComponent {
    // ...

    this.registerForm.valueChanges
        .filter(regForm => regForm.name.length > 2)
        .debounceTime(500)
        .distinctUntilChange()
        .subscribe(term => console.log(term))
}
register.component.ts

Only search when more than two characters.

Work together

// ...
import 'rxjs/add/operator/map';


@Injectable()
export class BackendService {
    // ...

    search(term: string) {
        return this.http.get('https://api.co/query=' + term )
                        .map(request => request.json().results)
    }
}
register.service.ts

http.get returns also an observable

Combine Observables

// ...
import 'rxjs/add/operator/flatMap';


export class RegisterComponent {
    constructor(private backendService: BackendService) {}

    // ...

    this.registerForm.valueChanges
        .filter(regForm => regForm.name.length > 2)
        .debounceTime(500)
        .distinctUntilChange()
        .flatMap(term => this.backendService.search(term))
        .subscribe(searchResult => this.items = searchResults)
}
register.component.ts

Merge observables from form and http.

Cancel unneeded requests

// ...
import 'rxjs/add/operator/switchMap';


export class RegisterComponent {
    // ...

    this.registerForm.valueChanges
        .filter(regForm => regForm.name.length > 2)
        .debounceTime(500)
        .distinctUntilChange()
        .switchMap(term => this.service.search(term))
        .subscribe(searchResult => this.items = searchResults)
}
register.component.ts

Automatically unsubscribe from the previous observables.

Retry http when not available

If a source Observable emits an error, resubscribe

// ...
import 'rxjs/add/operator/retry';


@Injectable()
export class BackendService {
    // ...

    search(term: string) {
        return this.http.get('https://api.co/query=' + term )
                        .retry(4)
                        .map(request => request.json().results)
    }
}
register.service.ts

Progressive attempts

@Injectable()
export class BackendService {
    // ...

    search(term: string) {
        return this.http.get('https://api.co/query=' + term )
                        .retryWhen(attempts => {
                          return Observable
                            .range(1, 4).zip(attempts, (i) => i)
                            .do(i => console.log('Next attempt in ' +i*i+ ' seconds ...'))
                            .flatMap(i => Observable.timer(i*i * 1000));
                        })
                        .map(request => request.json().results)
    }
}
register.service.ts
Next attempt in 1 seconds ...
Next attempt in 4 seconds ...
Next attempt in 9 seconds ...
Next attempt in 16 seconds ...

async improvements

export class RegisterComponent {
    // ...

    this.items$ = this.registerForm.valueChanges
        .filter(regForm => regForm.name.length > 2)
        .debounceTime(500)
        .distinctUntilChange()
        .switchMap(term => this.service.search(term))
}
register.component.ts

Async pipe subscribes and unsubscribes automatically.

register.component.ts
<ul>
  <li *ngFor="let item of items$ | async">{{item.name}}</li>
</ul>

Demo

Observable data services

Some definitions

Services
provide data to multiple parts of the application

 

Observable data
expose reactive data using Observables 

Some graphs

Backend service

 Store service

UI Component

UI Component

UI Component

Observable

Use a Subject internally

A Subject is both an Observable (so we can subscribe() to it) and an Observer (so we can call next() on it to emit a new value).

let behaviourSubject = new BehaviorSubject(initialState);

// ...

let currentValue = behaviorSubject.getValue();
@Injectable()
export class PortfolioStoreService {

    private _portfolio: BehaviorSubject<Portfolio> = new BehaviorSubject(emptyPortfolio);

    // ...
portfolio-store.service.ts

BehaviourSubject

BehaviorSubject is a variant of Subject.

It has the notion of "the current value"

@Injectable()
export class PortfolioStoreService {

    private _portfolio: BehaviorSubject<Portfolio>;
    portfolio$: Observable<Portfolio>

    constructor(private portfolioBackendService: PortfolioBackendService) {
        this.loadInitialData();
        _portfolio = new BehaviorSubject(emptyPortfolio);
        this.portfolio$ = this._portfolio.asObservable();
    }

    getPortfolio(): Observable<Portfolio> {
      return this.portfolio$;
    }

    // ...
portfolio-store.service.ts

Use a Subject internally !

Never offer the internal Subject,

always return an Observable.

@Injectable()
export class PortfolioStoreService {

    // ...

    addPortfolio(newPortfolio:Portfolio):Observable {

        let backendObs = this.portfolioBackendService.createPortfolio(newPortfolio);
        backendObs.subscribe(
            res => {
                this._portfolios.next(this._portfolios.getValue().concat(newPortfolio));
            });
        return backendObs;
    }

    // ...
portfolio-store.service.ts

Adding Portfolios

@Injectable()
export class PortfolioStoreService {

    // ...

    modifyPortfolio(newPortfolio:Portfolio): Observable {
        return this.portfolioBackendService.modifyPortfolio(newPortfolio)
                    .subscribe(
                        res => {
                            let portfolios = this._portfolios.getValue();
                            
                            portfolios = portfolios.map((portfolio: Portfolio) => 
                                (portfolio.id === newPortfolio.id) ? newPortfolio : portfolio);
            
                            this._portfolios.next(portfolios);
                        }
                    );
    }
    // ...
portfolio-store.service.ts

Modifying Portfolios

Best Practices

When building a Observable data services

  • Use a Rx Subject
  • Subject is basically an event bus
  • Do not expose the subject directly

Demo

Common mistakes

and best practices

Nothing is sent

Nothing is sent

Create a portfolio and send to backend server.

@Component({
    selector:'app-new-portfolio',
    template: `
        <form>
            ...
        </form>
        <button (click)="create()">Create Portfolio</button>
        `
})
export class NewPortfolioComponent {

    constructor(private portfolioService: PortfolioService) {
    }

    create() {
        this.portfolioService.createPortfolio(this.portfolio);
    }
}
new-portfolio.component.ts

Nothing is sent

Send a POST a message to server.


@Injectable()
export class PortfolioService {

    constructor(private http: Http) {
    }

    createPortfolio(portfolio) {
        const headers = new Headers()
                .append('Content-Type', 'application/json; charset=utf-8');
                .append('X-Requested-With','XMLHttpRequest');

        this.http.post('/portfolios', JSON.stringify({portfolio}), headers);
    }
}
portfolio.service.ts

What is happening?

COLD Observables

An observable is cold

if the producer isn't activated until subscription

Observables need a subscribe

Creating an observable is somewhat similar to declaring a function, the function itself is just a declaration.

export class PortfolioService {
    // ...

    return this.http.post('/portfolios', JSON.stringify({portfolio}), headers);
}
portfolio.service.ts
export class NewPortfolioComponent {
    // ...

    create() {
        this.portfolioService.createPortfolio(this.portfolio)
                .subscribe(
                    () => console.log('portolio saved successfully'),
                    console.error
                );
    }
}
new-portfolio.component.ts

Common mistakes

and best practices

Too many requests

Loving the async pipe

    ngOnInit() {
        this.portfolio$ = this.portfolioService.loadPortfolio(1);
    }
list-portfolio.component.ts
<div class="portfolio-detail" *ngIf="portfolio$ | async">
    <div class="portfolio-field">
        {{(portfolio$ | async).portfolioName}}
    </div>
    <div class="portfolio-field">
        {{(portfolio$ | async).portfolioCurrency}}
    </div>
    <div class="portfolio-field">
        {{( portfolio$ | async)?.longDescription}}
    </div>
</div>
list-portfolio.component.html
... /portfolios GET 200 OK 
... /portfolios GET 200 OK 
... /portfolios GET 200 OK 
... /portfolios GET 200 OK 

What is happening?

Too many queries

    ngOnInit() {
        this.portfolio$ = this.portfolioService.loadPortfolio(1);
    }
list-portfolio.component.ts
<div class="portfolio-detail" *ngIf="portfolio$ | async">
    <div class="portfolio-field">
        {{(portfolio$ | async).portfolioName}}
    </div>
    <div class="portfolio-field">
        {{(portfolio$ | async).portfolioCurrency}}
    </div>
    <div class="portfolio-field">
        {{( portfolio$ | async)?.longDescription}}
    </div>
</div>
list-portfolio.component.html

Each async pipe

subscribes and unsubscribes automatically

1st Solution: remove async

    ngOnInit() {
        this.portfolioService.loadPortfolio(1)
                .subscribe(result => this.portfolio = result);;
    }
list-portfolio.component.ts
<div class="portfolio-detail" *ngIf="portfolio">
    <div class="portfolio-field">
        {{ portfolio.portfolioName}}
    </div>
    <div class="portfolio-field">
        {{ portfolio.portfolioCurrency}}
    </div>
    <div class="portfolio-field">
        {{ portfolio?.longDescription}}
    </div>
</div>
list-portfolio.component.html

2nd Solution: refactor

  • Stateful or smart components store and change application-level information in memory.

 

  • Stateless or dumb components serve as a template, no state or Model is manipulated inside of them.

 

Organize components between

stateful vs stateless

Stateful component


export class ListPortfolioComponent implements OnInit {

    portfolioObs: Observable<Portfolio>;

    constructor(private portfolioService: portfolioService) {
    }

    ngOnInit() {
        this.portfolioObs = this.portfolioService.loadPortfolio(1);
    }
}
list-portfolio.component.ts

    <portfolio-detail [portfolio]="portfolioObs | async"></portfolio-detail>
list-portfolio.component.html

Stateless component


export class PortfolioDetailComponent {

    @Input() portfolio: Portfolio;

    constructor() {
    }
}
portfolio-detail.component.ts

<div class="portfolio-detail" *ngIf="portfolio">
    <div class="portfolio-field">
        {{ portfolio.portfolioName}}
    </div>
    <div class="portfolio-field">
        {{ portfolio.portfolioCurrency}}
    </div>
    <div class="portfolio-field">
        {{ portfolio?.longDescription}}
    </div>
</div>
portfolio-detail.component.html

Best practice!

Build loosely-coupled systems:

  • Extract the pure presentation logic into Stateless Components
  • Stateless or dumb components only use @Input and @Output

3rd Solution: Making it hot

Cold vs Hot observables

An observable is cold

if the producer isn't activated until subscription

An observable is hot

if the producer is activated outside of subscription

Publish and share

@Injectable()
export class PortfolioService {
    // ...
    constructor(private http: Http) { }

    loadPortfolio(portfolioId: string) {
        return this.http.get('https://api.co/portfolio?id=' + portfolioId )
                        .map(request => request.json().results)
                        .publish()
                        .refCount();
    }

}
portfolio.service.ts

As long as there is at least one Subscriber

this Observable will be emitting data.

@Injectable()
export class PortfolioService {
    // ...
    constructor(private http: Http) { }

    loadPortfolio(portfolioId: string) {
        return this.http.get('https://api.co/portfolio?id=' + portfolioId )
                        .map(request => request.json().results)
                        .share();
    }

}

4th Solution: improved *ngIf


<div class="portfolio-detail" *ngIf="portfolio$ | async; else loading; let portfolio">
    <div class="portfolio-field">
        {{ portfolio.portfolioName}}
    </div>
    <div class="portfolio-field">
        {{ portfolio.portfolioCurrency}}
    </div>
    <div class="portfolio-field">
        {{ portfolio?.longDescription}}
    </div>
</div>
list-portfolio.component.html

Discussion is still open in github.com/angular/angular/issues/15020

Only in Angular v4 Release Candidate

Conclusions

  • Observables are functions that tie observers to a producer
  • Subject is basically an event bus
  • async pipe subscribes and unsubscribes automatically
  • Extract the presentation logic into Stateless Components
  • Share resources with hot components
  • Do not use Observables for everything

Q&A

Gracias! - Spasiva

Observables in Angular

By Carlos Morales

Observables in Angular

Slides presented at Zürich Angular Meetup (https://www.meetup.com/AngularZRH/events/238138382/)

  • 915