The document discusses the benefits of using RxJS observables over promises and events for managing asynchronous and reactive code in Angular applications. It explains key concepts like observers, subscriptions, operators, cold vs hot observables, and using RxJS with services and components. Example code is provided for creating observable data services to share data between components, composing asynchronous logic with operators, and best practices for managing subscriptions and preventing memory leaks. Overall, the document promotes a reactive programming style with RxJS for building maintainable and testable Angular applications.
Introduction of Sandi K. Barr, a Senior Software Engineer, discussing Angular and RxJS.
Introduction to Observables: multiple values over time, lazy creation, and differences from Promises.
Explains how to convert DOM events into Observables and the basics of Observer and Subscription.
Introduction to Subscription and emphasizes the lack of guaranteed termination of Observables.
Difference between Cold and Hot Observables and introduction to Subject which acts as both observer and observable.
Introduction to BehaviorSubject and its basic illustration in a Todo Store Service.
Implementation of subscription in components and how to clean up subscriptions using Async Pipe.
Concept of Observable Data Services versus centralized state management, using Observables for HTTP requests.
Explains how HTTP Observables behave and techniques to handle them, including creating hot observables. Introduction to RxJS operators for complex asynchronous code, discussing pipeable and creation operators.
Usage of ngIf and async pipe in Angular, along with observable piping techniques.
Detailed operating methods in RxJS, including map, filter, reduce, and scan for data processing.Features of higher-order observables, utility operators like tap, and error handling with catchError.
Insights into various types of RxJS operators and troubleshooting tips on observable management.
Thank you note along with links to example code and slides shared.
Multiple values overtime
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
Cold ObsERvables
The produceris created during the subscription
https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
…
12.
HOT ObsERvables
The produceris created outside the subscription
https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
…
13.
SubjecT: both an
observableand an observer
Make a cold Observable hot
https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339
…
14.
Subject has state
Keepsa list of observers and sometimes also a number of the values that have been emitted
@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 youcan 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="lettodo 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="lettodo 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 DataService
Provide data to multiple parts of an application
Not the same as centralized state management
Observable
Data SERVICE
HTTP SERVICE
COMPONENTS
!=
22.
Action method updatesthe 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;
}
@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 codein 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
// using mapwith 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 thelatest 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$
}
});
catchError()
An error canterminate a stream and send the error to the error() callback,
or the stream can be allowed to continue if piped through catchError().
Custom Functions toAdd 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 Observablebeen
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?