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

More Related Content

What's hot (20)

PDF
Understanding react hooks
Samundra khatri
 
PPTX
Top 10 RxJs Operators in Angular
Jalpesh Vadgama
 
PDF
RxJS Evolved
trxcllnt
 
PDF
Use Node.js to create a REST API
Fabien Vauchelles
 
PPTX
Java 8 presentation
Van Huong
 
PDF
Introducing Async/Await
Valeri Karpov
 
PDF
RxJS - The Basics & The Future
Tracy Lee
 
PPTX
Angular modules in depth
Christoffer Noring
 
PPTX
Sharing Data Between Angular Components
Squash Apps Pvt Ltd
 
PPTX
React js programming concept
Tariqul islam
 
PPTX
Introduction to React JS for beginners
Varun Raj
 
PPTX
Express JS
Alok Guha
 
PDF
Java 8 features
NexThoughts Technologies
 
PDF
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Edureka!
 
PDF
React js
Rajesh Kolla
 
PDF
Introduction to React JS
Bethmi Gunasekara
 
PPTX
React JS - A quick introduction tutorial
Mohammed Fazuluddin
 
PDF
Asynchronous JavaScript Programming
Haim Michael
 
PDF
RxJS & Angular Reactive Forms @ Codemotion 2019
Fabio Biondi
 
Understanding react hooks
Samundra khatri
 
Top 10 RxJs Operators in Angular
Jalpesh Vadgama
 
RxJS Evolved
trxcllnt
 
Use Node.js to create a REST API
Fabien Vauchelles
 
Java 8 presentation
Van Huong
 
Introducing Async/Await
Valeri Karpov
 
RxJS - The Basics & The Future
Tracy Lee
 
Angular modules in depth
Christoffer Noring
 
Sharing Data Between Angular Components
Squash Apps Pvt Ltd
 
React js programming concept
Tariqul islam
 
Introduction to React JS for beginners
Varun Raj
 
Express JS
Alok Guha
 
Java 8 features
NexThoughts Technologies
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Edureka!
 
React js
Rajesh Kolla
 
Introduction to React JS
Bethmi Gunasekara
 
React JS - A quick introduction tutorial
Mohammed Fazuluddin
 
Asynchronous JavaScript Programming
Haim Michael
 
RxJS & Angular Reactive Forms @ Codemotion 2019
Fabio Biondi
 

Similar to Angular and The Case for RxJS (20)

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

Recently uploaded (20)

PPTX
Java Native Memory Leaks: The Hidden Villain Behind JVM Performance Issues
Tier1 app
 
PDF
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
PDF
Salesforce CRM Services.VALiNTRY360
VALiNTRY360
 
PDF
Digger Solo: Semantic search and maps for your local files
seanpedersen96
 
PPTX
Writing Better Code - Helping Developers make Decisions.pptx
Lorraine Steyn
 
PDF
Streamline Contractor Lifecycle- TECH EHS Solution
TECH EHS Solution
 
PPTX
Migrating Millions of Users with Debezium, Apache Kafka, and an Acyclic Synch...
MD Sayem Ahmed
 
PDF
vMix Pro 28.0.0.42 Download vMix Registration key Bundle
kulindacore
 
PPTX
Platform for Enterprise Solution - Java EE5
abhishekoza1981
 
PPTX
Tally software_Introduction_Presentation
AditiBansal54083
 
PDF
Build It, Buy It, or Already Got It? Make Smarter Martech Decisions
bbedford2
 
PPTX
An Introduction to ZAP by Checkmarx - Official Version
Simon Bennetts
 
PDF
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
PPTX
Fundamentals_of_Microservices_Architecture.pptx
MuhammadUzair504018
 
PDF
Alarm in Android-Scheduling Timed Tasks Using AlarmManager in Android.pdf
Nabin Dhakal
 
DOCX
Import Data Form Excel to Tally Services
Tally xperts
 
PDF
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
PPTX
Equipment Management Software BIS Safety UK.pptx
BIS Safety Software
 
PDF
Unlock Efficiency with Insurance Policy Administration Systems
Insurance Tech Services
 
PPTX
Agentic Automation Journey Session 1/5: Context Grounding and Autopilot for E...
klpathrudu
 
Java Native Memory Leaks: The Hidden Villain Behind JVM Performance Issues
Tier1 app
 
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
Salesforce CRM Services.VALiNTRY360
VALiNTRY360
 
Digger Solo: Semantic search and maps for your local files
seanpedersen96
 
Writing Better Code - Helping Developers make Decisions.pptx
Lorraine Steyn
 
Streamline Contractor Lifecycle- TECH EHS Solution
TECH EHS Solution
 
Migrating Millions of Users with Debezium, Apache Kafka, and an Acyclic Synch...
MD Sayem Ahmed
 
vMix Pro 28.0.0.42 Download vMix Registration key Bundle
kulindacore
 
Platform for Enterprise Solution - Java EE5
abhishekoza1981
 
Tally software_Introduction_Presentation
AditiBansal54083
 
Build It, Buy It, or Already Got It? Make Smarter Martech Decisions
bbedford2
 
An Introduction to ZAP by Checkmarx - Official Version
Simon Bennetts
 
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
Fundamentals_of_Microservices_Architecture.pptx
MuhammadUzair504018
 
Alarm in Android-Scheduling Timed Tasks Using AlarmManager in Android.pdf
Nabin Dhakal
 
Import Data Form Excel to Tally Services
Tally xperts
 
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
Equipment Management Software BIS Safety UK.pptx
BIS Safety Software
 
Unlock Efficiency with Insurance Policy Administration Systems
Insurance Tech Services
 
Agentic Automation Journey Session 1/5: Context Grounding and Autopilot for E...
klpathrudu
 
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