Developer 🥑 Advocate
& Senior Frontend Developer @Scalac
RxJS - crash course
Reactivity in @Components
Handling @Outputs with RxJS
Reactive @Inputs
Button
Component
Button
Component
const myButton = document.querySelector('#myButton');
const click$ = Rx.Observable.fromEvent(myButton, 'click');
click$.subscribe(event => console.log(event));
const mouseMove$ = fromEvent(window, 'mousemove');
mouseMove$.subscribe((event: MouseEvent) => {
console.log(`Mouse: (${event.clientX}, ${event.clientY})`)
});
const mouseSub = mouseMove$.subscribe((event: MouseEvent) => {
console.log(`Mouse: (${event.clientX}, ${event.clientY})`)
});
const clicksSub = click$.subscribe(() => {
mouseSub.unsubscribe();
clicksSub.unsubscribe();
});
! But we must remember to unsubscribe !
operators are what gives "super powers" to Observables
they allow us to manipulate the stream of data
some are similar to what you know from arrays - map, filter, reduceÂ
they can be chainedÂ
interval(1000).pipe(
map(x => x * x)
).subscribe(x => console.log(x)); // 1 4 9 ..
interval(1000).pipe(
filter(x => x % 2 === 0)
).subscribe(x => console.log(x)); // 2 4 6 ...
interval(1000).pipe(
filter(x => x % 2 === 0),
map(x => x * x)
).subscribe(x => console.log(x)); // 4 16 36 ...
create a stream of clicks on a window
map it to x coordinate of a click
emit only clicks that have x coordinate greater than 100 and lower than 500
create an Observable of triple clicks that returns the coordinates of the click in following format: (x, y)Â *
* UIEvent.detail
const myButton = document.querySelector('#myButton');
const click$ = Rx.Observable.fromEvent(myButton, 'click');
const mergeMapped$ = click$.pipe(
mergeMap(() => interval(1000))
);
mergeMapped$.subscribe(value => console.log(value));
create a timer (counting 1 to 5) and display its value in a div
timer, take operators
on button click start a timer created above
switchMap vs exhaustMap - play with it to see what's a difference
Then we can just use `componentValue` inside our component's template
this.ourObservable$.subscribe(value => {
this.componentValue = value;
});
@Component({
template: `
<div>{{ componentValue }}</div>
`
})
! But we must remember to unsubscribe !
subscription = new Subscription()
ngOnInit() {
this.subscription.add(
this.ourObservable$.subscribe(value => {
this.componentValue = value;
});
)
}
ngOnDestroy() {
this.subsctiption.unsubscribe();
}
! But we must remember to unsubscribe !
destroy$ = new Subject()
ngOnInit() {
this.ourObservable$
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
this.componentValue = value;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
Then we can use async pipe to unwrap the Observable
ourObservable$ = interval(1000);
@Component({
template: `
<div>{{ ourObservable$ | async }}</div>
`
})
No need for manual subscription management
async pipe “unwraps” the observable and returns a latest value
it takes care of unsubscribing by itself when component is destroyed
create an Observable which emit a current time (ie. every 500ms)
display then current time provided by this Observable using both methods (subscribe & async pipe)Â
@Component({
template: `
<input #myInput>
<div>{{ inputChanges$ | async }}</div>
`
})
inputChanges$: Observable<string>;
@ViewChild('myInput', { static: true }) myInput: ElementRef;
ngOnInit(): void {
this.inputChanges$ =
fromEvent(this.myInput.nativeElement, 'input').pipe(
map((event: any) => event.target.value)
);
}
Subject
Subject
@Component({
template: `
<input
#myInput
(change)=“inputValuesSubject.next(myInput.value)"
>
<div>{{ inputValuesSubject | async }}</div>
`
})
class MyComponent {
inputValuesSubject = new Subject<string>();
}
@Component({
template: `
<input
#firstArgument
(change)="firstArgSub.next(firstArgument.value)">
<input
#secondArgument
(change)="secondArgSub.next(secondArgument.value)">
<div>Sum {{ sum$ | async }}</div>
`
})
export class MySimpleCalculatorComponent {
firstArgSub = new Subject<string>();
firstArgSub = new Subject<string>();
sum$ = combineLatest(
[this.firstArgumentSubject, this.secondArgumentSubject]
).pipe(
map(([first, second]) => +first + +second)
);
}
// the only way to get notified
// of changes to this prop is `ngOnChanges`
@Input() firstProperty: string;
@Input() set secondProperty(secondProperty: string) {
// set some internal properties
// or do some calculation based on the property
}
// we use BehaviorSubject
// to always get the current value in new subscribers
reactiveProperty$ = new BehaviorSubject<string>(null);
@Input() set reactiveProperty(value: string) {
reactiveProperty$.next(value);
}
reactivePropertyCharacters$ = this.reactiveProperty$.pipe(
map(value => value.split('')),
map(characters => characters
.filter(char => !!char.match(/[a-zA-Z]/))
.map(char => char.toUpperCase())
)
)
computedProperty$ = combineLatest(
[propertyA$, propertyB$]
).pipe(
map(([a, b]) => a * b)
)
inside a component create an input text
use value typed inside the filter input to display only the recipes that contain given ingredients
allow for entering multiple ingredients (comma separated)
add a checkbox that determines if a recipe should contain all ingredients or just any of them
create a component with an array of recipes as an input
interface Recipe {
name: string;
ingredients: string[]
}
Â
Â