SlideShare a Scribd company logo
Angular and The
Case for RxJS
Sandi K. Barr
Senior Software Engineer
Multiple values over time
Cancellable with unsubscribe
Synchronous or asynchronous
Declarative: what, not how or when
Nothing happens without an observer
Separate chaining and subscription
Can be reused or retried
(not a spicy Promise)Observable
!! ! !!
Observable(lazy)
Promise(eager)
Create: new Observable((observer) => observer.next(123)); new Promise((resolve, reject) => resolve(123));
Transform: obs$.pipe(map((value) => value * 2)); promise.then((value) => value * 2);
Subscribe: sub = obs$.subscribe((value) => console.log(value)); promise.then((value) => console.log(value));
Unsubscribe: sub.unsubscribe(); // implied by promise resolution
https://angular.io/guide/comparing-observables#cheat-sheet
Observable
Event
button.addEventListener('click', e => console.log('Clicked', e));
button.removeEventListener('click');
https://angular.io/guide/comparing-observables#observables-compared-to-events-api
const clicks$ = fromEvent(button, 'click');
const subscription = clicks$.subscribe(event => console.log('Clicked', event));
subscription.unsubscribe();
Angular and The Case for RxJS
Observable
a function that takes
an Observer
const subscription = observable.subscribe(observer);
subscription.unsubscribe();
Observer
an object with next, error,
and complete methods
const observer = {
next: value => console.log('Next value:', value),
error: err => console.error('Error:', err),
complete: () => console.log('Complete')
};
const subscription = observable.subscribe(observer);
Subscription
an Observable only produces
values on subscribe()
A long time ago,
We used to be friends,
But I haven’t thought of
you lately at all...
Termination is not Guaranteed
https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
Cold ObsERvables
The producer is created during the subscription
https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
…
HOT ObsERvables
The producer is created outside the subscription
https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
…
SubjecT: both an
observable and an observer
Make a cold Observable hot
https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
…
Subject has state
Keeps a list of observers and sometimes also a number of the values that have been emitted
RxJS
Steep Learning Curve
@Injectable()
export class TodoStoreService {
private _todos: BehaviorSubject<Todo[]> = new BehaviorSubject([]);
constructor(private todoHTTP: TodoHttpService) {
this.loadData();
}
get todos(): Observable<Todo[]> {
return this._todos.asObservable();
}
loadData() {
this.todoAPI.getTodos()
.subscribe(
todos => this._todos.next(todos),
err => console.log('Error retrieving Todos’)
);
}
} https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts
Store: Observable Data Service
Provide data to multiple parts of an application
BehaviorSubject
Just because you can access the value directly...
https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-behavior-subject-getvalue-ts
const subject = new BehaviorSubject(initialValue);
// some time later…
const value = subject.getValue();
@Injectable()
export class TodoStoreService {
private _todo: BehaviorSubject<Todo[]> = new BehaviorSubject([]);
constructor(private todoAPI: TodoHttpService) {
this.loadData();
}
get todos(): Observable<Todo[]> {
return this._todos.asObservable();
}
}
https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts
Protect the BehaviorSubject with
asObservable()
Components can subscribe after the data arrives
@Component({
selector: 'app-todo-list',
template: `
<ul>
<app-todo-list-item
*ngFor="let todo of todos"
[todo]="todo">
</app-todo-list-item>
</ul>
`
})
export class TodoListComponent implements OnInit, OnDestroy {
todos: Todo[];
todosSub: Subscription;
constructor (private todoStore: TodoStoreService) {}
ngOnInit() {
this.todosSub = this.todoStore.todos.subscribe(todos => {
this.todos = todos;
});
}
ngOnDestroy() {
this.todosSub.unsubscribe();
}
}
LET ME
HAVE THAT
TODO List
CLEAN UP
SUBSCRIPTIONS
@Component({
selector: 'app-todo-list',
template: `
<ul>
<app-todo-list-item
*ngFor="let todo of todos$ | async"
[todo]="todo">
</app-todo-list-item>
</ul>
`
})
export class TodoListComponent {
todos$: Observable<Todo[]> = this.todoStore.todos;
constructor (private todoStore: TodoStoreService) {}
}
USE THE ASYNC PIPE TO
AVOID MEMORY LEAKS
Subscriptions live until a stream is completed or until they are manually unsubscribed
https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/components/todo-list.component.ts
Decouple responsibilities
Observable Data Service
Provide data to multiple parts of an application
Not the same as centralized state management
Observable
Data SERVICE
HTTP SERVICE
COMPONENTS
!=
Action method updates the store on success
Store: Observable Data Service
https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts
addTodo(newTodo: Todo): Observable<Todo> {
const observable = this.todoAPI.saveTodo(newTodo);
observable.pipe(
withLatestFrom(this.todos),
).subscribe(( [savedTodo, todos] ) => {
this._todos.next(todos.concat(savedTodo));
});
return observable;
}
Avoid duplicating HTTP requests!
https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts
HTTP Observables are Cold
addTodo(newTodo: Todo): Observable<Todo> {
const observable = this.todoAPI.saveTodo(newTodo);
observable.pipe(
withLatestFrom(this.todos),
).subscribe(( [savedTodo, todos] ) => {
this._todos.next(todos.concat(savedTodo));
});
return observable;
}
@Injectable()
export class TodoHttpService {
base = 'http://localhost:3000/todos';
constructor(private http: HttpClient) { }
getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.base);
}
saveTodo(newTodo: Todo): Observable<Todo> {
return this.http.post<Todo>(this.base, newTodo, {headers}).pipe(share());
}
}
share() makes a cold Observable hot
HTTP Service
https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/http/todo-http.service.ts
OPERATORS
Compose complex
asynchronous code in a
declarative manner
Pipeable Operators: take an input Observable and generate a resulting output Observable
Examples: filter, map, mergeMap
Creation Operators: standalone functions to create a new Observable
Examples: interval, of, fromEvent, concat
share() : refCount
share() also makes Observables retry-able
https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/http/todo-http.service.ts
@Injectable()
export class TodoHttpService {
base = 'http://localhost:3000/todos';
constructor(private http: HttpClient) { }
getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.base);
}
saveTodo(newTodo: Todo): Observable<Todo> {
return this.http.post<Todo>(this.base, newTodo, {headers}).pipe(share());
}
}
" HOT: Share the execution
⛄ COLD: Invoke the execution
async : Unsubscribes automatically
https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-duplicate-http-component-ts
But still be sure to avoid duplicating HTTP requests!
@Component({
selector: 'app-todo',
template: `
Total #: {{ total$ | async }}
<app-todo-list [todos]="todos$ | async"></app-todo-list>
`
})
export class DuplicateHttpTodoComponent {
todos$ = this.http.get<Todo[]>('http://localhost:3000/todos');
total$ = this.todos$.pipe(map(todos => todos.length));
constructor(private http: HttpClient) {}
}
ngIf with | async as
https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-async-as-component-ts
Assign Observable values to a local variable
@Component({
selector: 'app-todo',
template: `
<ng-container *ngIf="todos$ | async as todos">
Total #: {{ todos.length }}
<app-todo-list [todos]="todos"></app-todo-list>
</ng-container>
`
})
export class TodoComponent {
todos$ = this.http.get<Todo[]>('http://localhost:3000/todos');
constructor(private http: HttpClient) {}
}
ngIf with| async as with ;ngElse
https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-async-as-else-component-ts
Show alternate block when Observable has no value
@Component({
selector: 'app-todo',
template: `
<ng-container *ngIf="todos$ | async as todos; else loading">
Total #: {{ todos.length }}
<app-todo-list [todos]="todos"></app-todo-list>
</ng-container>
<ng-template #loading><p>Loading...</p></ng-template>
`
})
export class TodoComponent {
todos$ = this.http.get<Todo[]>('http://localhost:3000/todos');
constructor(private http: HttpClient) {}
}
Reactive Templates$ $
Compose a series of operators
Observable.prototype.pipe()
import { map, filter, scan } from 'rxjs/operators';
import { range } from 'rxjs';
const source$ = range(0, 10);
source$.pipe(
filter(x => x % 2 === 0),
map(x => x + x),
scan((acc, x) => acc + x, 0)
).subscribe(x => console.log(x));
https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-pipe-ts
https://rxjs-dev.firebaseapp.com/assets/images/guide/marble-diagram-anatomy.svg
map()
A transformational operator
Applies a projection to each value
filter()
ONE OF MANY FILTERING operatorS
Filters items emitted from source
find()
A CONDITIONAL operator
Finds the first match then completes
reduce()
AN Aggregate operator
Emits aggregate result on completion
Emits accumulated result at each interval
scan()
A TRANSFORMATIONAL operator
@Component({
selector: 'app-todo-search',
template: `
<label for="search">Search: </label>
<input id="search" (keyup)="onSearch($event.target.value)"/>
`
})
export class TodoSearchComponent implements OnInit, OnDestroy {
@Output() search = new EventEmitter<string>();
changeSub: Subscription;
searchStream = new Subject<string>();
ngOnInit() {
this.changeSub = this.searchStream.pipe(
filter(searchText => searchText.length > 2), // min length
debounceTime(300), // wait for break in keystrokes
distinctUntilChanged() // only if value changes
).subscribe(searchText => this.search.emit(searchText));
}
ngOnDestroy() {
if (this.changeSub) { this.changeSub.unsubscribe(); }
}
onSearch(searchText: string) {
this.searchStream.next(searchText);
}
} https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/components/todo-search.component.ts
TYPE
AHEAD
SEARCH:
Rate-limit the input and delay the output
debounceTime()
A FILTERING operator
Emits values that are distinct from the previous value
distinctUntilChanged()
A FILTERING operator
AVOID NESTED SUBSCRIPTIONS
pyramid shaped callback hell% %
export class TodoEditComponent implements OnInit {
todo: Todo;
constructor(private todoStore: TodoStoreService,
private route: ActivatedRoute,
private router: Router) {}
ngOnInit() {
this.route.params.subscribe(params => {
const id = +params['id'];
this.todoStore.todos.subscribe((todos: Todo[]) => {
this.todo = todos.find(todo => todo.id === id);
});
});
}
}
Higher-Order Observables
Observables that emit other Observables
https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/components/edit/todo-edit.component.ts
export class TodoEditComponent {
todo$: Observable<Todo> = this.route.params.pipe(
map(params => +params['id']),
switchMap(id => this.todoStore.getTodoById(id))
);
constructor(private todoStore: TodoStoreService,
private route: ActivatedRoute,
private router: Router) {}
}
Cancels inner Observables
switch
Waits for inner Observables to complete
CONCAT
Subscribe to multiple inner Observables at a time
MERGE
// using map with nested subscribe
from([1, 2, 3, 4]).pipe(
map(param => getData(param))
).subscribe(val => val.subscribe(data => console.log(data)));
// using map and mergeAll
from([1, 2, 3, 4]).pipe(
map(param => getData(param)),
mergeAll()
).subscribe(val => console.log(val));
// using mergeMap
from([1, 2, 3, 4]).pipe(
mergeMap(param => getData(param))
).subscribe(val => console.log(val));
mergeMap()
Higher-order transformation operator
Luuk Gruijs: https://medium.com/@luukgruijs/understanding-rxjs-map-mergemap-switchmap-and-concatmap-833fc1fb09ff
Projects each source value into an Observable that is merged into the output Observable
Also provide the latest value from another Observable
withLatestFrom()
combineLatest()
Updates from the latest values of each input Observable
https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-combinelatest-withlatestfrom-ts
combineLatest( [notifications$, otherPrimaryActiities$] )
.subscribe({
next: ( [notification, otherPrimaryActivity] ) => {
// fires whenever notifications$ _or_ otherPrimaryActivities$ updates
// but not until all sources have emitted at least one value
}
});
notifications$.pipe( withLatestFrom(mostRecentUpdates$) )
.subscribe({
next: ( [notification, mostRecentUpdate] ) => {
// fires only when notifications$ updates and includes latest from mostRecentUpdates$
}
});
@Component({
selector: 'app-many-subscriptions',
template: `<p>value 1: {{value1}}</p>
<p>value 2: {{value2}}</p>
<p>value 3: {{value3}}</p>`
})
export class SubscriberComponent implements OnInit, OnDestroy {
value1: number;
value2: number;
value3: number;
destroySubject$: Subject<void> = new Subject();
constructor(private service: MyService) {}
ngOnInit() {
this.service.value1.pipe(
takeUntil(this.destroySubject$)
).subscribe(value => {
this.value1 = value;
});
this.service.value2.pipe(
takeUntil(this.destroySubject$)
).subscribe(value => {
this.value2 = value;
});
this.service.value3.pipe(
takeUntil(this.destroySubject$)
).subscribe(value => {
this.value3 = value;
});
}
ngOnDestroy() {
this.destroySubject$.next();
}
}
Always
Bring
Backup
with this
one weird trick
USE A SUBJECT
TO Complete
STREAMS
https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-weird-trick-takeuntil-ts-L1
tap()
a UTILITY operator
Perform side effects and return the stream unchanged
catchError()
an error handling operator
Catch errors in the stream and return a new Observable
catchError()
An error can terminate a stream and send the error to the error() callback,
or the stream can be allowed to continue if piped through catchError().
https://rxjs-dev.firebaseapp.com/guide/operators
Creation
fromEvent
interval
of
Aggregate
reduce
count
Conditional
find
every
isEmpty
Utility
tap
delay
toArray
Error Handling
catchError
retry
JOIN
mergeAll
withLatestFrom
Filtering
filter
debounceTime
distinctUntilChanged
Transformation
map
mergeMap
scan
Join Creation
merge
concat
combineLatest
Multicasting
share
publish
publishReplay
Aggregate
reduce
count
Conditional
find
every
isEmpty
Utility
tap
delay
toArray
Error Handling
catchError
retry
JOIN
mergeAll
withLatestFrom
Filtering
filter
debounceTime
distinctUntilChanged
Join Creation
merge
concat
combineLatest
Transformation
map
mergeMap
scan Creation
fromEvent
interval
of
Multicasting
share
publish
publishReplay
https://rxjs-dev.firebaseapp.com/guide/operators
Custom Functions to Add and Remove Event Handlers
generate
interval
fromEventPattern
fromEvent
Create a New
Observable
Sequence
That Works Like a for-Loop
That Never Does Anything
That Repeats a Value
That Throws an Error
That Completes
From an Event
From a Promise
That Iterates
That Emits Values on a Timer
Decided at Subscribe Time
Based on a Boolean Condition
Over Values in a Numeric Range
Over Values in an Iterable or Array-like Sequence
Over Arguments
With an Optional Delay
Using Custom Logic
Of Object Key/Values
repeat
throwError
EMPTY
NEVER
from
pairs
range
of
timer
iif
defer
usingThat Depends on a Resource
Troubleshooting
Has this Observable been
subscribed to?

How many Subscriptions
does this Observable have?

When does this Observable
complete? Does it complete?

Do I need to unsubscribe
from this Observable?
THANKS!
example code: https://github.com/sandikbarr/rxjs-todo
slides: https://www.slideshare.net/secret/FL6NONZJ7DDAkf
Ad

Recommended

Introduction to RxJS
Introduction to RxJS
Brainhub
 
Angular 2 observables
Angular 2 observables
Geoffrey Filippi
 
Top 10 RxJs Operators in Angular
Top 10 RxJs Operators in Angular
Jalpesh Vadgama
 
Angular & RXJS: examples and use cases
Angular & RXJS: examples and use cases
Fabio Biondi
 
Angular Observables & RxJS Introduction
Angular Observables & RxJS Introduction
Rahat Khanna a.k.a mAppMechanic
 
Introduction to RxJS
Introduction to RxJS
Abul Hasan
 
Rxjs ppt
Rxjs ppt
Christoffer Noring
 
RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)
Tracy Lee
 
Intro to React
Intro to React
Justin Reock
 
React hooks
React hooks
Ramy ElBasyouni
 
Reactjs
Reactjs
Mallikarjuna G D
 
Workshop 21: React Router
Workshop 21: React Router
Visual Engineering
 
react redux.pdf
react redux.pdf
Knoldus Inc.
 
React Router: React Meetup XXL
React Router: React Meetup XXL
Rob Gietema
 
JavaScript Tutorial
JavaScript Tutorial
Bui Kiet
 
Asynchronous JavaScript Programming
Asynchronous JavaScript Programming
Haim Michael
 
JavaScript Promises
JavaScript Promises
L&T Technology Services Limited
 
Advanced javascript
Advanced javascript
Doeun KOCH
 
ReactJS presentation.pptx
ReactJS presentation.pptx
DivyanshGupta922023
 
JavaScript Promises
JavaScript Promises
Derek Willian Stavis
 
React + Redux Introduction
React + Redux Introduction
Nikolaus Graf
 
RxJS Evolved
RxJS Evolved
trxcllnt
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Edureka!
 
Introduction into ES6 JavaScript.
Introduction into ES6 JavaScript.
boyney123
 
JavaScript - Chapter 11 - Events
JavaScript - Chapter 11 - Events
WebStackAcademy
 
Javascript Module Patterns
Javascript Module Patterns
Nicholas Jansma
 
React-JS Component Life-cycle Methods
React-JS Component Life-cycle Methods
ANKUSH CHAVAN
 
React js
React js
Rajesh Kolla
 
Rxjs swetugg
Rxjs swetugg
Christoffer Noring
 
Rxjs marble-testing
Rxjs marble-testing
Christoffer Noring
 

More Related Content

What's hot (20)

Intro to React
Intro to React
Justin Reock
 
React hooks
React hooks
Ramy ElBasyouni
 
Reactjs
Reactjs
Mallikarjuna G D
 
Workshop 21: React Router
Workshop 21: React Router
Visual Engineering
 
react redux.pdf
react redux.pdf
Knoldus Inc.
 
React Router: React Meetup XXL
React Router: React Meetup XXL
Rob Gietema
 
JavaScript Tutorial
JavaScript Tutorial
Bui Kiet
 
Asynchronous JavaScript Programming
Asynchronous JavaScript Programming
Haim Michael
 
JavaScript Promises
JavaScript Promises
L&T Technology Services Limited
 
Advanced javascript
Advanced javascript
Doeun KOCH
 
ReactJS presentation.pptx
ReactJS presentation.pptx
DivyanshGupta922023
 
JavaScript Promises
JavaScript Promises
Derek Willian Stavis
 
React + Redux Introduction
React + Redux Introduction
Nikolaus Graf
 
RxJS Evolved
RxJS Evolved
trxcllnt
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Edureka!
 
Introduction into ES6 JavaScript.
Introduction into ES6 JavaScript.
boyney123
 
JavaScript - Chapter 11 - Events
JavaScript - Chapter 11 - Events
WebStackAcademy
 
Javascript Module Patterns
Javascript Module Patterns
Nicholas Jansma
 
React-JS Component Life-cycle Methods
React-JS Component Life-cycle Methods
ANKUSH CHAVAN
 
React js
React js
Rajesh Kolla
 
React Router: React Meetup XXL
React Router: React Meetup XXL
Rob Gietema
 
JavaScript Tutorial
JavaScript Tutorial
Bui Kiet
 
Asynchronous JavaScript Programming
Asynchronous JavaScript Programming
Haim Michael
 
Advanced javascript
Advanced javascript
Doeun KOCH
 
React + Redux Introduction
React + Redux Introduction
Nikolaus Graf
 
RxJS Evolved
RxJS Evolved
trxcllnt
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Edureka!
 
Introduction into ES6 JavaScript.
Introduction into ES6 JavaScript.
boyney123
 
JavaScript - Chapter 11 - Events
JavaScript - Chapter 11 - Events
WebStackAcademy
 
Javascript Module Patterns
Javascript Module Patterns
Nicholas Jansma
 
React-JS Component Life-cycle Methods
React-JS Component Life-cycle Methods
ANKUSH CHAVAN
 

Similar to Angular and The Case for RxJS (20)

Rxjs swetugg
Rxjs swetugg
Christoffer Noring
 
Rxjs marble-testing
Rxjs marble-testing
Christoffer Noring
 
Angular2 rxjs
Angular2 rxjs
Christoffer Noring
 
Rxjs ngvikings
Rxjs ngvikings
Christoffer Noring
 
RxJS In-Depth - AngularConnect 2015
RxJS In-Depth - AngularConnect 2015
Ben Lesh
 
Observables in Angular
Observables in Angular
Knoldus Inc.
 
RxJava@Android
RxJava@Android
Maxim Volgin
 
RxJS - The Reactive extensions for JavaScript
RxJS - The Reactive extensions for JavaScript
Viliam Elischer
 
Angular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app example
Alexey Frolov
 
Taming Asynchrony using RxJS
Taming Asynchrony using RxJS
Angelo Simone Scotto
 
WebCamp:Front-end Developers Day. Александр Мостовенко "Rx.js - делаем асинхр...
WebCamp:Front-end Developers Day. Александр Мостовенко "Rx.js - делаем асинхр...
GeeksLab Odessa
 
rx.js make async programming simpler
rx.js make async programming simpler
Alexander Mostovenko
 
Angular observables for fun and profit
Angular observables for fun and profit
Gil Steiner
 
Samuele Resca - REACTIVE PROGRAMMING, DAMN. IT IS NOT ABOUT REACTJS - Codemot...
Samuele Resca - REACTIVE PROGRAMMING, DAMN. IT IS NOT ABOUT REACTJS - Codemot...
Codemotion
 
Rx – reactive extensions
Rx – reactive extensions
Voislav Mishevski
 
Angular 2 Architecture (Bucharest 26/10/2016)
Angular 2 Architecture (Bucharest 26/10/2016)
Eyal Vardi
 
Reactive programming with rx java
Reactive programming with rx java
CongTrung Vnit
 
Rxjs kyivjs 2015
Rxjs kyivjs 2015
Alexander Mostovenko
 
Mattia Manzati - Real-World MobX Project Architecture - Codemotion Rome 2019
Mattia Manzati - Real-World MobX Project Architecture - Codemotion Rome 2019
Codemotion
 
Reactive programming with RxJava
Reactive programming with RxJava
Jobaer Chowdhury
 
RxJS In-Depth - AngularConnect 2015
RxJS In-Depth - AngularConnect 2015
Ben Lesh
 
Observables in Angular
Observables in Angular
Knoldus Inc.
 
RxJS - The Reactive extensions for JavaScript
RxJS - The Reactive extensions for JavaScript
Viliam Elischer
 
Angular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app example
Alexey Frolov
 
WebCamp:Front-end Developers Day. Александр Мостовенко "Rx.js - делаем асинхр...
WebCamp:Front-end Developers Day. Александр Мостовенко "Rx.js - делаем асинхр...
GeeksLab Odessa
 
rx.js make async programming simpler
rx.js make async programming simpler
Alexander Mostovenko
 
Angular observables for fun and profit
Angular observables for fun and profit
Gil Steiner
 
Samuele Resca - REACTIVE PROGRAMMING, DAMN. IT IS NOT ABOUT REACTJS - Codemot...
Samuele Resca - REACTIVE PROGRAMMING, DAMN. IT IS NOT ABOUT REACTJS - Codemot...
Codemotion
 
Angular 2 Architecture (Bucharest 26/10/2016)
Angular 2 Architecture (Bucharest 26/10/2016)
Eyal Vardi
 
Reactive programming with rx java
Reactive programming with rx java
CongTrung Vnit
 
Mattia Manzati - Real-World MobX Project Architecture - Codemotion Rome 2019
Mattia Manzati - Real-World MobX Project Architecture - Codemotion Rome 2019
Codemotion
 
Reactive programming with RxJava
Reactive programming with RxJava
Jobaer Chowdhury
 
Ad

Recently uploaded (20)

On-Device AI: Is It Time to Go All-In, or Do We Still Need the Cloud?
On-Device AI: Is It Time to Go All-In, or Do We Still Need the Cloud?
Hassan Abid
 
Emvigo Capability Deck 2025: Accelerating Innovation Through Intelligent Soft...
Emvigo Capability Deck 2025: Accelerating Innovation Through Intelligent Soft...
Emvigo Technologies
 
Streamlining CI/CD with FME Flow: A Practical Guide
Streamlining CI/CD with FME Flow: A Practical Guide
Safe Software
 
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
BradBedford3
 
UPDASP a project coordination unit ......
UPDASP a project coordination unit ......
withrj1
 
Open Source Software Development Methods
Open Source Software Development Methods
VICTOR MAESTRE RAMIREZ
 
GDG Douglas - Google AI Agents: Your Next Intern?
GDG Douglas - Google AI Agents: Your Next Intern?
felipeceotto
 
SAP PM Module Level-IV Training Complete.ppt
SAP PM Module Level-IV Training Complete.ppt
MuhammadShaheryar36
 
Download Adobe Illustrator Crack free for Windows 2025?
Download Adobe Illustrator Crack free for Windows 2025?
grete1122g
 
MOVIE RECOMMENDATION SYSTEM, UDUMULA GOPI REDDY, Y24MC13085.pptx
MOVIE RECOMMENDATION SYSTEM, UDUMULA GOPI REDDY, Y24MC13085.pptx
Maharshi Mallela
 
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Natan Silnitsky
 
Artificial Intelligence Workloads and Data Center Management
Artificial Intelligence Workloads and Data Center Management
SandeepKS52
 
Sysinfo OST to PST Converter Infographic
Sysinfo OST to PST Converter Infographic
SysInfo Tools
 
Step by step guide to install Flutter and Dart
Step by step guide to install Flutter and Dart
S Pranav (Deepu)
 
Folding Cheat Sheet # 9 - List Unfolding 𝑢𝑛𝑓𝑜𝑙𝑑 as the Computational Dual of ...
Folding Cheat Sheet # 9 - List Unfolding 𝑢𝑛𝑓𝑜𝑙𝑑 as the Computational Dual of ...
Philip Schwarz
 
Code and No-Code Journeys: The Coverage Overlook
Code and No-Code Journeys: The Coverage Overlook
Applitools
 
A Guide to Telemedicine Software Development.pdf
A Guide to Telemedicine Software Development.pdf
Olivero Bozzelli
 
NVIDIA Artificial Intelligence Ecosystem and Workflows
NVIDIA Artificial Intelligence Ecosystem and Workflows
SandeepKS52
 
ElectraSuite_Prsentation(online voting system).pptx
ElectraSuite_Prsentation(online voting system).pptx
mrsinankhan01
 
Introduction to Agile Frameworks for Product Managers.pdf
Introduction to Agile Frameworks for Product Managers.pdf
Ali Vahed
 
On-Device AI: Is It Time to Go All-In, or Do We Still Need the Cloud?
On-Device AI: Is It Time to Go All-In, or Do We Still Need the Cloud?
Hassan Abid
 
Emvigo Capability Deck 2025: Accelerating Innovation Through Intelligent Soft...
Emvigo Capability Deck 2025: Accelerating Innovation Through Intelligent Soft...
Emvigo Technologies
 
Streamlining CI/CD with FME Flow: A Practical Guide
Streamlining CI/CD with FME Flow: A Practical Guide
Safe Software
 
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
BradBedford3
 
UPDASP a project coordination unit ......
UPDASP a project coordination unit ......
withrj1
 
Open Source Software Development Methods
Open Source Software Development Methods
VICTOR MAESTRE RAMIREZ
 
GDG Douglas - Google AI Agents: Your Next Intern?
GDG Douglas - Google AI Agents: Your Next Intern?
felipeceotto
 
SAP PM Module Level-IV Training Complete.ppt
SAP PM Module Level-IV Training Complete.ppt
MuhammadShaheryar36
 
Download Adobe Illustrator Crack free for Windows 2025?
Download Adobe Illustrator Crack free for Windows 2025?
grete1122g
 
MOVIE RECOMMENDATION SYSTEM, UDUMULA GOPI REDDY, Y24MC13085.pptx
MOVIE RECOMMENDATION SYSTEM, UDUMULA GOPI REDDY, Y24MC13085.pptx
Maharshi Mallela
 
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Natan Silnitsky
 
Artificial Intelligence Workloads and Data Center Management
Artificial Intelligence Workloads and Data Center Management
SandeepKS52
 
Sysinfo OST to PST Converter Infographic
Sysinfo OST to PST Converter Infographic
SysInfo Tools
 
Step by step guide to install Flutter and Dart
Step by step guide to install Flutter and Dart
S Pranav (Deepu)
 
Folding Cheat Sheet # 9 - List Unfolding 𝑢𝑛𝑓𝑜𝑙𝑑 as the Computational Dual of ...
Folding Cheat Sheet # 9 - List Unfolding 𝑢𝑛𝑓𝑜𝑙𝑑 as the Computational Dual of ...
Philip Schwarz
 
Code and No-Code Journeys: The Coverage Overlook
Code and No-Code Journeys: The Coverage Overlook
Applitools
 
A Guide to Telemedicine Software Development.pdf
A Guide to Telemedicine Software Development.pdf
Olivero Bozzelli
 
NVIDIA Artificial Intelligence Ecosystem and Workflows
NVIDIA Artificial Intelligence Ecosystem and Workflows
SandeepKS52
 
ElectraSuite_Prsentation(online voting system).pptx
ElectraSuite_Prsentation(online voting system).pptx
mrsinankhan01
 
Introduction to Agile Frameworks for Product Managers.pdf
Introduction to Agile Frameworks for Product Managers.pdf
Ali Vahed
 
Ad

Angular and The Case for RxJS

  • 1. Angular and The Case for RxJS Sandi K. Barr Senior Software Engineer
  • 2. Multiple values over time Cancellable with unsubscribe Synchronous or asynchronous Declarative: what, not how or when Nothing happens without an observer Separate chaining and subscription Can be reused or retried (not a spicy Promise)Observable !! ! !!
  • 3. Observable(lazy) Promise(eager) Create: new Observable((observer) => observer.next(123)); new Promise((resolve, reject) => resolve(123)); Transform: obs$.pipe(map((value) => value * 2)); promise.then((value) => value * 2); Subscribe: sub = obs$.subscribe((value) => console.log(value)); promise.then((value) => console.log(value)); Unsubscribe: sub.unsubscribe(); // implied by promise resolution https://angular.io/guide/comparing-observables#cheat-sheet
  • 4. Observable Event button.addEventListener('click', e => console.log('Clicked', e)); button.removeEventListener('click'); https://angular.io/guide/comparing-observables#observables-compared-to-events-api const clicks$ = fromEvent(button, 'click'); const subscription = clicks$.subscribe(event => console.log('Clicked', event)); subscription.unsubscribe();
  • 6. Observable a function that takes an Observer const subscription = observable.subscribe(observer); subscription.unsubscribe();
  • 7. Observer an object with next, error, and complete methods const observer = { next: value => console.log('Next value:', value), error: err => console.error('Error:', err), complete: () => console.log('Complete') }; const subscription = observable.subscribe(observer);
  • 8. Subscription an Observable only produces values on subscribe() A long time ago, We used to be friends, But I haven’t thought of you lately at all...
  • 9. Termination is not Guaranteed
  • 11. Cold ObsERvables The producer is created during the subscription https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339 …
  • 12. HOT ObsERvables The producer is created outside the subscription https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339 …
  • 13. SubjecT: both an observable and an observer Make a cold Observable hot https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339 …
  • 14. Subject has state Keeps a list of observers and sometimes also a number of the values that have been emitted
  • 16. @Injectable() export class TodoStoreService { private _todos: BehaviorSubject<Todo[]> = new BehaviorSubject([]); constructor(private todoHTTP: TodoHttpService) { this.loadData(); } get todos(): Observable<Todo[]> { return this._todos.asObservable(); } loadData() { this.todoAPI.getTodos() .subscribe( todos => this._todos.next(todos), err => console.log('Error retrieving Todos’) ); } } https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts Store: Observable Data Service Provide data to multiple parts of an application
  • 17. BehaviorSubject Just because you can access the value directly... https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-behavior-subject-getvalue-ts const subject = new BehaviorSubject(initialValue); // some time later… const value = subject.getValue();
  • 18. @Injectable() export class TodoStoreService { private _todo: BehaviorSubject<Todo[]> = new BehaviorSubject([]); constructor(private todoAPI: TodoHttpService) { this.loadData(); } get todos(): Observable<Todo[]> { return this._todos.asObservable(); } } https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts Protect the BehaviorSubject with asObservable() Components can subscribe after the data arrives
  • 19. @Component({ selector: 'app-todo-list', template: ` <ul> <app-todo-list-item *ngFor="let todo of todos" [todo]="todo"> </app-todo-list-item> </ul> ` }) export class TodoListComponent implements OnInit, OnDestroy { todos: Todo[]; todosSub: Subscription; constructor (private todoStore: TodoStoreService) {} ngOnInit() { this.todosSub = this.todoStore.todos.subscribe(todos => { this.todos = todos; }); } ngOnDestroy() { this.todosSub.unsubscribe(); } } LET ME HAVE THAT TODO List CLEAN UP SUBSCRIPTIONS
  • 20. @Component({ selector: 'app-todo-list', template: ` <ul> <app-todo-list-item *ngFor="let todo of todos$ | async" [todo]="todo"> </app-todo-list-item> </ul> ` }) export class TodoListComponent { todos$: Observable<Todo[]> = this.todoStore.todos; constructor (private todoStore: TodoStoreService) {} } USE THE ASYNC PIPE TO AVOID MEMORY LEAKS Subscriptions live until a stream is completed or until they are manually unsubscribed https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/components/todo-list.component.ts
  • 21. Decouple responsibilities Observable Data Service Provide data to multiple parts of an application Not the same as centralized state management Observable Data SERVICE HTTP SERVICE COMPONENTS !=
  • 22. Action method updates the store on success Store: Observable Data Service https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts addTodo(newTodo: Todo): Observable<Todo> { const observable = this.todoAPI.saveTodo(newTodo); observable.pipe( withLatestFrom(this.todos), ).subscribe(( [savedTodo, todos] ) => { this._todos.next(todos.concat(savedTodo)); }); return observable; }
  • 23. Avoid duplicating HTTP requests! https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/store/todo-store.service.ts HTTP Observables are Cold addTodo(newTodo: Todo): Observable<Todo> { const observable = this.todoAPI.saveTodo(newTodo); observable.pipe( withLatestFrom(this.todos), ).subscribe(( [savedTodo, todos] ) => { this._todos.next(todos.concat(savedTodo)); }); return observable; }
  • 24. @Injectable() export class TodoHttpService { base = 'http://localhost:3000/todos'; constructor(private http: HttpClient) { } getTodos(): Observable<Todo[]> { return this.http.get<Todo[]>(this.base); } saveTodo(newTodo: Todo): Observable<Todo> { return this.http.post<Todo>(this.base, newTodo, {headers}).pipe(share()); } } share() makes a cold Observable hot HTTP Service https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/http/todo-http.service.ts
  • 25. OPERATORS Compose complex asynchronous code in a declarative manner Pipeable Operators: take an input Observable and generate a resulting output Observable Examples: filter, map, mergeMap Creation Operators: standalone functions to create a new Observable Examples: interval, of, fromEvent, concat
  • 26. share() : refCount share() also makes Observables retry-able https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/http/todo-http.service.ts @Injectable() export class TodoHttpService { base = 'http://localhost:3000/todos'; constructor(private http: HttpClient) { } getTodos(): Observable<Todo[]> { return this.http.get<Todo[]>(this.base); } saveTodo(newTodo: Todo): Observable<Todo> { return this.http.post<Todo>(this.base, newTodo, {headers}).pipe(share()); } }
  • 27. " HOT: Share the execution ⛄ COLD: Invoke the execution
  • 28. async : Unsubscribes automatically https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-duplicate-http-component-ts But still be sure to avoid duplicating HTTP requests! @Component({ selector: 'app-todo', template: ` Total #: {{ total$ | async }} <app-todo-list [todos]="todos$ | async"></app-todo-list> ` }) export class DuplicateHttpTodoComponent { todos$ = this.http.get<Todo[]>('http://localhost:3000/todos'); total$ = this.todos$.pipe(map(todos => todos.length)); constructor(private http: HttpClient) {} }
  • 29. ngIf with | async as https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-async-as-component-ts Assign Observable values to a local variable @Component({ selector: 'app-todo', template: ` <ng-container *ngIf="todos$ | async as todos"> Total #: {{ todos.length }} <app-todo-list [todos]="todos"></app-todo-list> </ng-container> ` }) export class TodoComponent { todos$ = this.http.get<Todo[]>('http://localhost:3000/todos'); constructor(private http: HttpClient) {} }
  • 30. ngIf with| async as with ;ngElse https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-async-as-else-component-ts Show alternate block when Observable has no value @Component({ selector: 'app-todo', template: ` <ng-container *ngIf="todos$ | async as todos; else loading"> Total #: {{ todos.length }} <app-todo-list [todos]="todos"></app-todo-list> </ng-container> <ng-template #loading><p>Loading...</p></ng-template> ` }) export class TodoComponent { todos$ = this.http.get<Todo[]>('http://localhost:3000/todos'); constructor(private http: HttpClient) {} }
  • 32. Compose a series of operators Observable.prototype.pipe() import { map, filter, scan } from 'rxjs/operators'; import { range } from 'rxjs'; const source$ = range(0, 10); source$.pipe( filter(x => x % 2 === 0), map(x => x + x), scan((acc, x) => acc + x, 0) ).subscribe(x => console.log(x)); https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-pipe-ts
  • 34. map() A transformational operator Applies a projection to each value
  • 35. filter() ONE OF MANY FILTERING operatorS Filters items emitted from source
  • 36. find() A CONDITIONAL operator Finds the first match then completes
  • 37. reduce() AN Aggregate operator Emits aggregate result on completion
  • 38. Emits accumulated result at each interval scan() A TRANSFORMATIONAL operator
  • 39. @Component({ selector: 'app-todo-search', template: ` <label for="search">Search: </label> <input id="search" (keyup)="onSearch($event.target.value)"/> ` }) export class TodoSearchComponent implements OnInit, OnDestroy { @Output() search = new EventEmitter<string>(); changeSub: Subscription; searchStream = new Subject<string>(); ngOnInit() { this.changeSub = this.searchStream.pipe( filter(searchText => searchText.length > 2), // min length debounceTime(300), // wait for break in keystrokes distinctUntilChanged() // only if value changes ).subscribe(searchText => this.search.emit(searchText)); } ngOnDestroy() { if (this.changeSub) { this.changeSub.unsubscribe(); } } onSearch(searchText: string) { this.searchStream.next(searchText); } } https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/components/todo-search.component.ts TYPE AHEAD SEARCH:
  • 40. Rate-limit the input and delay the output debounceTime() A FILTERING operator
  • 41. Emits values that are distinct from the previous value distinctUntilChanged() A FILTERING operator
  • 42. AVOID NESTED SUBSCRIPTIONS pyramid shaped callback hell% % export class TodoEditComponent implements OnInit { todo: Todo; constructor(private todoStore: TodoStoreService, private route: ActivatedRoute, private router: Router) {} ngOnInit() { this.route.params.subscribe(params => { const id = +params['id']; this.todoStore.todos.subscribe((todos: Todo[]) => { this.todo = todos.find(todo => todo.id === id); }); }); } }
  • 43. Higher-Order Observables Observables that emit other Observables https://github.com/sandikbarr/rxjs-todo/blob/master/src/app/todo/components/edit/todo-edit.component.ts export class TodoEditComponent { todo$: Observable<Todo> = this.route.params.pipe( map(params => +params['id']), switchMap(id => this.todoStore.getTodoById(id)) ); constructor(private todoStore: TodoStoreService, private route: ActivatedRoute, private router: Router) {} }
  • 45. Waits for inner Observables to complete CONCAT
  • 46. Subscribe to multiple inner Observables at a time MERGE
  • 47. // using map with nested subscribe from([1, 2, 3, 4]).pipe( map(param => getData(param)) ).subscribe(val => val.subscribe(data => console.log(data))); // using map and mergeAll from([1, 2, 3, 4]).pipe( map(param => getData(param)), mergeAll() ).subscribe(val => console.log(val)); // using mergeMap from([1, 2, 3, 4]).pipe( mergeMap(param => getData(param)) ).subscribe(val => console.log(val)); mergeMap() Higher-order transformation operator Luuk Gruijs: https://medium.com/@luukgruijs/understanding-rxjs-map-mergemap-switchmap-and-concatmap-833fc1fb09ff Projects each source value into an Observable that is merged into the output Observable
  • 48. Also provide the latest value from another Observable withLatestFrom() combineLatest() Updates from the latest values of each input Observable https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-combinelatest-withlatestfrom-ts combineLatest( [notifications$, otherPrimaryActiities$] ) .subscribe({ next: ( [notification, otherPrimaryActivity] ) => { // fires whenever notifications$ _or_ otherPrimaryActivities$ updates // but not until all sources have emitted at least one value } }); notifications$.pipe( withLatestFrom(mostRecentUpdates$) ) .subscribe({ next: ( [notification, mostRecentUpdate] ) => { // fires only when notifications$ updates and includes latest from mostRecentUpdates$ } });
  • 49. @Component({ selector: 'app-many-subscriptions', template: `<p>value 1: {{value1}}</p> <p>value 2: {{value2}}</p> <p>value 3: {{value3}}</p>` }) export class SubscriberComponent implements OnInit, OnDestroy { value1: number; value2: number; value3: number; destroySubject$: Subject<void> = new Subject(); constructor(private service: MyService) {} ngOnInit() { this.service.value1.pipe( takeUntil(this.destroySubject$) ).subscribe(value => { this.value1 = value; }); this.service.value2.pipe( takeUntil(this.destroySubject$) ).subscribe(value => { this.value2 = value; }); this.service.value3.pipe( takeUntil(this.destroySubject$) ).subscribe(value => { this.value3 = value; }); } ngOnDestroy() { this.destroySubject$.next(); } } Always Bring Backup with this one weird trick USE A SUBJECT TO Complete STREAMS https://gist.github.com/sandikbarr/36bb0bf0b99a82a74be92aba1b1d0482#file-weird-trick-takeuntil-ts-L1
  • 50. tap() a UTILITY operator Perform side effects and return the stream unchanged
  • 51. catchError() an error handling operator Catch errors in the stream and return a new Observable
  • 52. catchError() An error can terminate a stream and send the error to the error() callback, or the stream can be allowed to continue if piped through catchError().
  • 55. Custom Functions to Add and Remove Event Handlers generate interval fromEventPattern fromEvent Create a New Observable Sequence That Works Like a for-Loop That Never Does Anything That Repeats a Value That Throws an Error That Completes From an Event From a Promise That Iterates That Emits Values on a Timer Decided at Subscribe Time Based on a Boolean Condition Over Values in a Numeric Range Over Values in an Iterable or Array-like Sequence Over Arguments With an Optional Delay Using Custom Logic Of Object Key/Values repeat throwError EMPTY NEVER from pairs range of timer iif defer usingThat Depends on a Resource
  • 56. Troubleshooting Has this Observable been subscribed to? How many Subscriptions does this Observable have? When does this Observable complete? Does it complete? Do I need to unsubscribe from this Observable?
  • 57. THANKS! example code: https://github.com/sandikbarr/rxjs-todo slides: https://www.slideshare.net/secret/FL6NONZJ7DDAkf