Wojciech Trawiński
Poznań, 2019
JavaScript developer working at 7N for Roche company.
Passionate of Angular, RxJS, TypeScript and functional programming.
Owner of JavaScript everyday blog
Writer for Angular In Depth and Angular Love blogs
JavaScript developer working at 7N for Roche company.
Passionate of Angular, RxJS, TypeScript and functional programming.
... a declarative programming paradigm
concerned with data streams and the propagation of change.
... a declarative programming paradigm
concerned with data streams and the propagation of change.
Object Oriented Programming
Reactive Programming
everything is a class
everything is a stream
In a nutshell:
In a nutshell:
Observable
Observer
subscribe
notifications
Types of observable notifications:
Types of observable notifications:
An observer may be interested in different kinds of notifications
and corresponding callbacks may be provided in the following ways:
Types of observable notifications:
An observer may be interested in different kinds of notifications
and corresponding callbacks may be provided in the following ways:
as object
interval(1000).subscribe({
next: console.log,
error: console.error,
complete: () => console.log('completed')
});
Types of observable notifications:
An observer may be interested in different kinds of notifications
and corresponding callbacks may be provided in the following ways:
as object
interval(1000).subscribe({
next: console.log,
error: console.error,
complete: () => console.log('completed')
});
as successive arguments
interval(1000).subscribe(
console.log,
console.error,
() => console.log('completed')
);
A representation of any set of values
over any amount of time
A representation of any set of values
over any amount of time
RxJS provides a large number of creational operators:
… [multicasting] is the primary use case for Subjects in RxJS
… [multicasting] is the primary use case for Subjects in RxJS
import { Subject, interval } from 'rxjs';
const source$ = interval(1000);
const mySubject = new Subject();
source$.subscribe(mySubject);
mySubject.subscribe(val => console.log(`#1 ${val}`));
setTimeout(() => {
mySubject.subscribe(val => console.log(`#2 ${val}`));
}, 5000);
A function which accepts a stream and returns a new observable
A function which accepts a stream and returns a new observable
import { interval } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
const fastSource$ = interval(10);
const result$ = fastSource$.pipe(
throttleTime(2000)
);
result$.subscribe(console.log);
Task description
export class UserService {
private _apiUrl = 'your-api-url';
private _isLoading = true;
private _user: User | null = null;
get isLoading(): boolean {
return this._isLoading;
}
get hasUser(): boolean {
return this._user !== null;
}
get user(): User | null {
return this._user ? { ...this._user } : null;
}
}
Service
export interface User {
id: number;
name: string;
username: string;
email: string;
}
Model
export class UserService {
private _apiUrl = 'your-api-url';
private _isLoading = true;
private _user: User | null = null;
get isLoading(): boolean {
return this._isLoading;
}
get hasUser(): boolean {
return this._user !== null;
}
get user(): User | null {
return this._user ? { ...this._user } : null;
}
}
Service
export class UserService {
private _apiUrl = 'your-api-url';
private _isLoading = true;
private _user: User | null = null;
get isLoading(): boolean {
return this._isLoading;
}
get hasUser(): boolean {
return this._user !== null;
}
get user(): User | null {
return this._user ? { ...this._user } : null;
}
loadUser(id: string) {
this._isLoading = true;
fetch(`${this.apiUrl}/users/${id}`)
.then(user => user.json())
.then(user => {
this._user = user;
this._isLoading = false;
});
}
}
Service
export interface User {
id: number;
name: string;
username: string;
email: string;
}
Model
const userService = new UserService();
function render() {
appDiv.innerHTML = `
-loading: ${userService.isLoading} <br>
-has user: ${userService.hasUser} <br>
-user: ${JSON.stringify(userService.user)}
`
}
Usage
export interface User {
id: number;
name: string;
username: string;
email: string;
}
Model
export class UserService {
private _apiUrl = 'your-api-url';
private _isLoading = true;
private _user: User | null = null;
get isLoading(): boolean {
return this._isLoading;
}
get hasUser(): boolean {
return this._user !== null;
}
get user(): User | null {
return this._user ? { ...this._user } : null;
}
loadUser(id: string) {
this._isLoading = true;
fetch(`${this.apiUrl}/users/${id}`)
.then(user => user.json())
.then(user => {
this._user = user;
this._isLoading = false;
});
}
}
Service
Advantages:
Advantages:
Disadvantages:
Service
export class UserService {
private apiUrl = 'your-api-url';
private loadingSubject = new BehaviorSubject(true);
private userIdSubject = new Subject<string>();
readonly user$ = ...
readonly isLoading$ = this.loadingSubject.pipe(
distinctUntilChanged()
);
readonly hasUser$ = this.user$.pipe(
map(Boolean)
);
loadUser(id: string) {
this.userIdSubject.next(id);
}
}
Service
export interface User {
id: number;
name: string;
username: string;
email: string;
}
Model
export class UserService {
private apiUrl = 'your-api-url';
private loadingSubject = new BehaviorSubject(true);
private userIdSubject = new Subject<string>();
readonly user$ = ...
readonly isLoading$ = this.loadingSubject.pipe(
distinctUntilChanged()
);
readonly hasUser$ = this.user$.pipe(
map(Boolean)
);
loadUser(id: string) {
this.userIdSubject.next(id);
}
}
Service
export interface User {
id: number;
name: string;
username: string;
email: string;
}
Model
readonly user$ = this.userIdSubject.pipe(
tap(() => this.setLoadingFlag(true)),
switchMap(id => this.getUserStream(id)),
tap(() => this.setLoadingFlag(false)),
startWith(null),
shareReplay(1)
);
User stream
export class UserService {
private apiUrl = 'your-api-url';
private loadingSubject = new BehaviorSubject(true);
private userIdSubject = new Subject<string>();
readonly user$ = ...
readonly isLoading$ = this.loadingSubject.pipe(
distinctUntilChanged()
);
readonly hasUser$ = this.user$.pipe(
map(Boolean)
);
loadUser(id: string) {
this.userIdSubject.next(id);
}
}
Service
export interface User {
id: number;
name: string;
username: string;
email: string;
}
Model
readonly user$ = this.userIdSubject.pipe(
tap(() => this.setLoadingFlag(true)),
switchMap(id => this.getUserStream(id)),
tap(() => this.setLoadingFlag(false)),
startWith(null),
shareReplay(1)
);
User stream
private setLoadingFlag(isLoading: boolean) {
this.loadingSubject.next(isLoading);
}
export class UserService {
private apiUrl = 'your-api-url';
private loadingSubject = new BehaviorSubject(true);
private userIdSubject = new Subject<string>();
readonly user$ = ...
readonly isLoading$ = this.loadingSubject.pipe(
distinctUntilChanged()
);
readonly hasUser$ = this.user$.pipe(
map(Boolean)
);
loadUser(id: string) {
this.userIdSubject.next(id);
}
}
export class UserService {
private apiUrl = 'your-api-url';
private loadingSubject = new BehaviorSubject(true);
private userIdSubject = new Subject<string>();
readonly user$ = ...
readonly isLoading$ = this.loadingSubject.pipe(
distinctUntilChanged()
);
readonly hasUser$ = this.user$.pipe(
map(Boolean)
);
loadUser(id: string) {
this.userIdSubject.next(id);
}
}
Service
export interface User {
id: number;
name: string;
username: string;
email: string;
}
Model
readonly user$ = this.userIdSubject.pipe(
tap(() => this.setLoadingFlag(true)),
switchMap(id => this.getUserStream(id)),
tap(() => this.setLoadingFlag(false)),
startWith(null),
shareReplay(1)
);
User stream
private setLoadingFlag(isLoading: boolean) {
this.loadingSubject.next(isLoading);
}
private getUserStream(id: string): Observable<User> {
return ajax(`${this.apiUrl}/users/${id}`).pipe(
map(({ response }) => response)
);
}
Implementing remaining task requirements
readonly user$ = this.userIdSubject.pipe(
debounceTime(500),
tap(() => this.setLoadingFlag(true)),
switchMap(id => this.getUserStream(id)),
tap(() => this.setLoadingFlag(false)),
startWith(null),
shareReplay(1)
);
Implementing remaining task requirements
500 ms have to elapse
after a user stopped typing
before making an http request
readonly user$ = this.userIdSubject.pipe(
debounceTime(500),
tap(() => this.setLoadingFlag(true)),
switchMap(id => this.getUserStream(id)),
tap(() => this.setLoadingFlag(false)),
startWith(null),
shareReplay(1)
);
Implementing remaining task requirements
readonly user$ = this.userIdSubject.pipe(
debounceTime(500),
distinctUntilChanged(),
tap(() => this.setLoadingFlag(true)),
switchMap(id => this.getUserStream(id)),
tap(() => this.setLoadingFlag(false)),
startWith(null),
shareReplay(1)
);
500 ms have to elapse
after a user stopped typing
before making an http request
Http request shouldn’t be made if a query string hasn’t changed
Advantages:
Advantages:
Disadvantages:
wojtrawi@gmail.com