Observables in Angular
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/2899703/angular2-logo.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/3596160/Screen_Shot_2017-03-16_at_01.00.53.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/3597107/observer.png)
Observables are functions that tie observers to a producer
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/3596294/reactivity.png)
Observers in JS
Object.observe feature is obsolete. Use Proxy instead.
Babel does not support Proxies (cannot be transpiled or polyfilled).
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/3597118/Screen_Shot_2017-03-16_at_09.34.35.png)
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"
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/3612107/S.BehaviorSubject.png)
@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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/1611187/chris-rock-huh-face.gif)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/299454/images/3598175/disaster-girl2.jpg)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/carlosmorales/images/1219306/careto_grafiti.jpg)
Observables in Angular
By Carlos Morales
Observables in Angular
Slides presented at Zürich Angular Meetup (https://www.meetup.com/AngularZRH/events/238138382/)
- 915