rxjs | Cookbook
Informations
NGRX – Reactive State for Angular
Original Post is here. Github repo has the code snippets at choopage’s GitHub repo.
Recipes
Chaining of observable
The below snippet would return 0, 1, 2, 3…, n until it is stopped. It would be returned at every 2 sec. See reference here.
import { Observable } <strong>from 'rxjs/Rx'; let obs = Observable.<em>interval</em>(2000); let req = obs .flatMap(v => { return Observable.of(v) }) .subscribe( v => console.log(v), err => console.error(err), () => console.log(<strong>'done'</strong>) );
Response transformer
The below would return undefined at every 2 sec interval.
import { Observable } from 'rxjs/Rx'; let obs = Observable.interval(2000); obs .let(responseTransformer) .subscribe( v => console.log(v), err=> console.error(err) ); responseTransformer(input: Observable<any>) { return input.map(v => v.data); }
Using RxJS composition over base class extension
export class MyAPI { constructor(private http: Http) { } get(url: string, options: any) { return this.http.get(url, options).let(responseTransformer); } } responseTransformer(input: Observable<any>) { return input.map(v => v.data); }
Higher Order Observable
We create higher order observable using .map.
const numObservable = Rx.Observable.interval(1000).take(4); const higherOrderObservable = numObservable.map(x => Rx.Observable.of(1, 2)); higherOrderObservable.subscribe(<br>x => x.subscribe(y => console.log(y)));
Further use of higher order observable
usingHigherOrderObservable() { Observable .interval(1000) .groupBy(n => n }
Flatten a higher order observable with RxJS switch
const numObservable = Rx.Observable.interval(1000).take(2); const higherOrderObservable = numObservable.map(x => Rx.Observable.of(1, 2)).switch(); /* ------+--------+--------- \ \ 1,2 1,2 switch ------1-2-------1-2------- Switch map flattens the higher order observable */ higherOrderObservable.subscribe(x => console.log(x));
switchMap: map and flatten higher order observables
const clickObservable = Rx.Observable.fromEvent(document, 'click'); function performRequest() { return fetch('<a href="http://jsonplaceholder.typicode.com/users/1'" target="_blank" rel="noreferrer noopener">http://jsonplaceholder.typicode.com/users/1'</a>) .then(res =>; res.json()); //this returns a Promise } //Observabl<Event> --> Observable<Response><br>const responseObservable = clickObservable<br> .switchMap(click => performRequest());//switchMap can convert Promise to Observable<br>//switchMap = map .... + ... switchresponseObservable.subscribe(x => console.log(x.email));
Use groupBy in real RxJS applications
See reference here.
const busObservable = Rx.Observable.of( {code: 'en-us', value: '-TEST-'}, {code: 'en-us', value: 'hello'}, {code: 'es', value: '-TEST-'}, {code: 'en-us', value: 'amazing'}, {code: 'pt-br', value: '-TEST-'}, {code: 'pt-br', value: 'olá'}, {code: 'es', value: 'hola'}, {code: 'es', value: 'mundo'}, {code: 'en-us', value: 'world'}, {code: 'pt-br', value: 'mundo'}, {code: 'es', value: 'asombroso'}, {code: 'pt-br', value: 'maravilhoso'} ).concatMap(x => Rx.Observable.of(x).delay(500));const all = busObservable .groupBy(obj => obj.code); .mergeMap(innerObs => innerObs.skip(1).map(obj => obj.value));//Alternatively could using filter and map /* const enUS = busObservable .filter(obj => obj.code === 'en-us') .map(obj => obj.value);const es = busObservable .filter(obj => obj.code === 'es') .map(obj => obj.value);const all = Rx.Observable.merge(enUS, es); */all.subscribe(x => console.log(x));
Using .map versus .switchMap
The below code snippet we can view the result of using .map versus .switchMap
//user.service.ts import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import { Observable } from "rxjs"; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; @Injectable() export class UserService { constructor(private http: Http) { } getUsers(): Observable<any> { return this.http.get('http://jsonplaceholder.typicode.com/users') //.map(v => v.json()); .switchMap(v => v.json()); } } //app.component.ts import { Component } from '@angular/core'; import { UserService } from "./user.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app works!'; constructor(private userService: UserService) { } search(term: string) { this.userService.getUsers() .subscribe(v => console.log(v)); /* //we can do this with .switchMap this.userService.getUsers() .subscribe(v => {if (v.email != "Sincere@april.biz") { console.log(v.email); }}); */ } }
Solving the multiple Async Pipe in Angular ≥ 2.0.0 with share operator
Remember to import import "rxjs/add/operator/share";
See reference here.
squareData$: Observable<string> = Observable.range(0, 10) .map(x => x * x) .do(x => console.log(`CalculationResult: ${x}`) .toArray() .map(squares => squares.join(", ")) .share(); // remove this line: console will log every result 3 times instead of 1
Managing Cold and Hot Observables using publish().refCount() which is similar to .share()
ngOnInit() { // in angular 2 and above component.ts file add these this.coldObservable(); this.hotObservable(); } /* * cold observable is like a recast of video * */ coldObservable() { let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1); incrementalObs.subscribe(val => console.log('a: ' + val)); setTimeout(function() { incrementalObs.subscribe(val => console.log(' b: ' + val)); }, 4500); } /* * hot observable is like watching a live video * */ hotObservable() { let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1).publish().refCount(); //can also use .share() incrementalObs.subscribe(val => console.log('a: ' + val)); setTimeout(function() { incrementalObs.subscribe(val => console.log(' b: ' + val)); }, 4500); }
Observables Array Operations with flatMap
Reference Rangle.io article.
getLoadList(): void { this.shareService .fetchLoad() .take(1) .filter(response => { if ( response.status === 200 ) { return true; } else if ( response.status === 304 ) { // do something more return false; } else { this.gotoErrorPage(); return false; } }) .flatMap(response => response.data.loads as Load[]) .filter(obj => obj.content.contentGrade === 'x') .subscribe( val => console.log(val), err => { console.error(err); }); }
Error Handling
Error handling in RxJS
Some learning points from RxJS lesson videos. This repo is available in my GitHub repo.
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { BehaviorSubject } from "rxjs/BehaviorSubject"; import 'rxjs/add/observable/bindCallback'; import 'rxjs/add/observable/bindNodeCallback'; import 'rxjs/add/observable/combineLatest'; import 'rxjs/add/observable/concat'; import 'rxjs/add/observable/defer'; import 'rxjs/add/observable/empty'; import 'rxjs/add/observable/forkJoin'; import 'rxjs/add/observable/from'; import 'rxjs/add/observable/fromEvent'; import 'rxjs/add/observable/fromEventPattern'; import 'rxjs/add/observable/fromPromise'; import 'rxjs/add/observable/generate'; import 'rxjs/add/observable/if'; import 'rxjs/add/observable/interval'; import 'rxjs/add/observable/merge'; import 'rxjs/add/observable/race'; import 'rxjs/add/observable/never'; import 'rxjs/add/observable/of'; import 'rxjs/add/observable/onErrorResumeNext'; import 'rxjs/add/observable/pairs'; import 'rxjs/add/observable/range'; import 'rxjs/add/observable/using'; import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/timer'; import 'rxjs/add/observable/zip'; import 'rxjs/add/observable/dom/ajax'; import 'rxjs/add/observable/dom/webSocket'; import 'rxjs/add/operator/buffer'; import 'rxjs/add/operator/bufferCount'; import 'rxjs/add/operator/bufferTime'; import 'rxjs/add/operator/bufferToggle'; import 'rxjs/add/operator/bufferWhen'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/combineAll'; import 'rxjs/add/operator/combineLatest'; import 'rxjs/add/operator/concat'; import 'rxjs/add/operator/concatAll'; import 'rxjs/add/operator/concatMap'; import 'rxjs/add/operator/concatMapTo'; import 'rxjs/add/operator/count'; import 'rxjs/add/operator/dematerialize'; import 'rxjs/add/operator/debounce'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/defaultIfEmpty'; import 'rxjs/add/operator/delay'; import 'rxjs/add/operator/delayWhen'; import 'rxjs/add/operator/distinct'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/distinctUntilKeyChanged'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/exhaust'; import 'rxjs/add/operator/exhaustMap'; import 'rxjs/add/operator/expand'; import 'rxjs/add/operator/elementAt'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/finally'; import 'rxjs/add/operator/find'; import 'rxjs/add/operator/findIndex'; import 'rxjs/add/operator/first'; import 'rxjs/add/operator/groupBy'; import 'rxjs/add/operator/ignoreElements'; import 'rxjs/add/operator/isEmpty'; import 'rxjs/add/operator/audit'; import 'rxjs/add/operator/auditTime'; import 'rxjs/add/operator/last'; import 'rxjs/add/operator/let'; import 'rxjs/add/operator/every'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/mapTo'; import 'rxjs/add/operator/materialize'; import 'rxjs/add/operator/max'; import 'rxjs/add/operator/merge'; import 'rxjs/add/operator/mergeAll'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/mergeMapTo'; import 'rxjs/add/operator/mergeScan'; import 'rxjs/add/operator/min'; import 'rxjs/add/operator/multicast'; import 'rxjs/add/operator/observeOn'; import 'rxjs/add/operator/onErrorResumeNext'; import 'rxjs/add/operator/pairwise'; import 'rxjs/add/operator/partition'; import 'rxjs/add/operator/pluck'; import 'rxjs/add/operator/publish'; import 'rxjs/add/operator/publishBehavior'; import 'rxjs/add/operator/publishReplay'; import 'rxjs/add/operator/publishLast'; import 'rxjs/add/operator/race'; import 'rxjs/add/operator/reduce'; import 'rxjs/add/operator/repeat'; import 'rxjs/add/operator/repeatWhen'; import 'rxjs/add/operator/retry'; import 'rxjs/add/operator/retryWhen'; import 'rxjs/add/operator/sample'; import 'rxjs/add/operator/sampleTime'; import 'rxjs/add/operator/scan'; import 'rxjs/add/operator/sequenceEqual'; import 'rxjs/add/operator/share'; import 'rxjs/add/operator/single'; import 'rxjs/add/operator/skip'; import 'rxjs/add/operator/skipUntil'; import 'rxjs/add/operator/skipWhile'; import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/subscribeOn'; import 'rxjs/add/operator/switch'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/switchMapTo'; import 'rxjs/add/operator/take'; import 'rxjs/add/operator/takeLast'; import 'rxjs/add/operator/takeUntil'; import 'rxjs/add/operator/takeWhile'; import 'rxjs/add/operator/throttle'; import 'rxjs/add/operator/throttleTime'; import 'rxjs/add/operator/timeInterval'; import 'rxjs/add/operator/timeout'; import 'rxjs/add/operator/timeoutWith'; import 'rxjs/add/operator/timestamp'; import 'rxjs/add/operator/toArray'; import 'rxjs/add/operator/toPromise'; import 'rxjs/add/operator/window'; import 'rxjs/add/operator/windowCount'; import 'rxjs/add/operator/windowTime'; import 'rxjs/add/operator/windowToggle'; import 'rxjs/add/operator/windowWhen'; import 'rxjs/add/operator/withLatestFrom'; import 'rxjs/add/operator/zip'; import 'rxjs/add/operator/zipAll';
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: [ './app.component.css' ] }) export class AppComponent implements OnInit { title = 'app works!'; obs = Observable.of(1, 2, 3, 4); ngOnInit() { this.howToHandleErrorV1(); this.howToHandleErrorV2(); this.howToUseRetry(); this.mergeObservableAndThrowError(); this.mergeObservableAndErrorResumeNext(); this.mergeObservableAndErrorCatch(); } /* * This uses Catch for V1. This introduces Closure. It is effectively the same as V2. * */ howToHandleErrorV1() { this.obs .map(x => { if ( x === 3 ) { throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error } return x; }) .catch(err => Observable.throw('Caught error here Observable.throw')) // continue go down the error path use Observable.throw .catch(err => Observable.of('Caught error here Observable.of')) // catch just use Observable.of .subscribe( x => console.log(x), err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here () => console.log('done completed') ); } /* * There is a difference between V1 and V2. For V2 it is using onErrorResumeNext which * */ howToHandleErrorV2() { let good = Observable.of('Caught error here Observable.of'); this.obs .map(x => { if ( x === 3 ) { throw 'I hate threes'; // When it hit error it actually unsubscribe itself at x === 3 of throw error } return x; }) .onErrorResumeNext(good) // To catch just use Observable.of .subscribe( x => console.log(x), err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here () => console.log('done completed') ); } /* * For this we use see it retries three times then console.error(err); * So retryWhen is for trying network connection websocket * */ howToUseRetry() { this.obs .map(x => { if ( x === 3 ) { throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error } return x; }) .retry(3) // retry three times .retryWhen(err => err.delay(2000).take(3)) // similar but with 2 seconds delay and the error is not propagated. .retryWhen(err => err.delay(2000).take(3).concat(Observable.throw('bad'))) // this it would throw an error. .subscribe( x => console.log(x), err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here () => console.log('done completed') ); } /* * Using observable merge operator * */ mergeObservableAndThrowError() { let mergedObs = Observable.merge( this.obs, //1, 2, 3, 4 Observable.throw('Stop Error'), Observable.from(this.array), //0, 1, 2, 3, 4, 5 Observable.of(999) //999, ); mergedObs.subscribe( val => console.log(val), //this should show 1, 2, 3, 4, Stop Error error => console.log(error), () => console.log("completed") ); } /* Using observable onErrorResumeNext just like merge operator * */ mergeObservableAndErrorResumeNext() { let mergedObs = Observable.onErrorResumeNext( this.obs, //1, 2, 3, 4 Observable.throw('Stop Error'), Observable.from(this.array), //0, 1, 2, 3, 4, 5 Observable.of(999) //999, ); mergedObs.subscribe( val => console.log(val), //this should show 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 999 error => console.log(error), () => console.log("completed") ); } /* * Using observable merge operator and catch * */ mergeObservableAndErrorCatch() { let mergedObs = Observable.merge( this.obs, //1, 2, 3, 4 Observable.throw('Stop Error'), Observable.from(this.array), //0, 1, 2, 3, 4, 5 Observable.of(999) //999, ).catch(e => { console.log(e); return Observable.of('catch error here'); }); mergedObs.subscribe( val => console.log(val), //this should show 1, 2, 3, 4, Stop Error, Catch Error Here error => console.log(error), () => console.log("completed") ); } }
map vs flatMap in RxJS
Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable. See my GitHub repo.
obs = Observable.of(1, 2, 3, 4); ngOnInit() { this.usingMap(); this.usingMapToMakeInnerObservable(); this.usingMapAndMergeAll(); this.usingFlatMap(); }usingMap() { this.obs .map(x => x * 2) // transform the input by multiple of 2 .subscribe( x => console.log(x), err => console.error(err), () => console.log('done completed') ); } usingMapToMakeInnerObservable() { this.obs .map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3 //.map(x => Observable.timer(500).map((x) => x + 3)) // !!! REMEMBER Not the same as the immediate above .subscribe( x => console.log(x), err => console.error(err), () => console.log('done completed') ); } // Map and Merge all is the same as just one FlatMap usingMapAndMergeAll() { this.obs .map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3 .mergeAll() .subscribe( x => console.log(x), err => console.error(err), () => console.log('done completed') ); } // Flat map is the same as map then merge all // transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable usingFlatMap() { this.obs .flatMap(x => Observable.timer(500).map(() => x + 10)) // transform the input wrapping it with another observable and addition of 10 .subscribe( x => console.log(x), err => console.error(err), () => console.log('done completed') ); }
Transforming pure Javascript array vs. Observable from array
array = [0, 1, 2, 3, 4, 5]; ngOnInit() { this.setArrayToObservableThenTransform(); }/* * This keeps creating new array. It is good that it creates new array of arr for immutability. * But it's bad because there is clean up and resource intensive for mobile * */ transformArray() { let result = this.array .filter(( x, i, arr ) => { console.log('filtering ' + x); console.log('is the source array ' + (arr === this.array)); return x }) .map(( x, i, arr ) => { console.log('mapping ' + x); console.log('is the source array ' + (arr === this.array)); return x + '!'; }) .reduce(( r, x, i, arr ) => { console.log('reducing ' + x); return r + x; }, '--'); console.log(result); } /* * This is more efficient for resource management because it linearly scans and discard when not right * */ setArrayToObservableThenTransform() { let obsArray = Observable.from(this.array); // Use Observable.from() instead of Observable.of(). There is diff. obsArray .filter(( x: any ) => { console.log('filtering ' + x); return x }) .map(( x ) => { console.log('mapping ' + x); return x + '!'; }) .reduce(( r, x ) => { console.log('reducing ' + x); return r + x; }, '--') .subscribe( x => console.log(x) ); }
Using reduce and scan to aggregate RxJs data
array = [0, 1, 2, 3, 4, 5];ngOnInit() { this.reduceArray(); this.reduceObservableArray(); this.reduceObservableArray_Abstract2(); this.scanObservableArray(); } /* * This is the same as reduceObservableArray() * */ reduceArray() { let result = this.array.reduce( (accumulator, currentValue) => accumulator + currentValue, 3 ); // 3 is the init value. console.log('reduceArray ' + result); // output 18 => 3 + (0 ... 5) } /* * This is the same as reduceArray() * But this waits for all the arrays to finish emitting before reducing them to one single number * See the next method to understand better * */ reduceObservableArray() { let obsArray = Observable.from(this.array); obsArray.reduce( (accumulator, currentValue) => accumulator + currentValue, 3 ).subscribe( val => console.log('reduceObservableArray ' + val) ); } /* * The exact same reduce function/method as of reduceObserableArray() above * This proves that it waits for all 6 numbers to come in then reduce them * */ reduceObservableArray_Abstract2() { let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5 obsArray.reduce( (accumulator, currentValue) => accumulator + currentValue, 3 ).subscribe( val => console.log('reduceObservableArray_Abstract2 ' + val) ); } /* * This is the same as the above reduceObserableArray_Abstract2() * except this is using scan instead of reduce * */ scanObservableArray() { let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5 obsArray.scan( (accumulator, currentValue) => accumulator + currentValue, 3 ).subscribe( val => console.log('scanObservableArray() ' + val) ); }
Create, next, and subscribe to Subject and BehaviorSubject
There is a Stack Overflow thread which discussed about the difference between Subject and BehaviorSubject. It’s worth understanding.
Also see my personal GitHub for source code.
app.component.ts
import { Subject } from 'rxjs/Subject'; import { BehaviorSubject } from "rxjs/BehaviorSubject"; // create subject // there is no need for initial value subject = new Subject<boolean>(); // create behaviorSubject which require initial value // true is an initial value. if there is a subscription // after this, it would get true value immediately behaviorSubject = new BehaviorSubject<boolean>(true); ngOnInit() { this.subject.next(false); /* Subject subscription wont get anything at this point before the subscribeSubject() */ this.subscribeSubject(); this.subscribeBehaviorSubject(); } /* * Push the next val into the behavior subject * */ nextSubject(val: boolean) { this.subject.next(val); } /* * Any values push into the subject would not be can shown * before this subscribeSubject() is called * */ subscribeSubject() { this.subject //.take(1) //when we include .take(1) we will have a complete. Without this it will continue subscribing .subscribe( val => console.log(val), err => console.error(err), () => console.log('completed') ); } /* * This is the proper way to return a subject as observable * */ getSubject(): Observable<boolean> { return this.subject.asObservable(); } /* * Push the next val into the behavior subject * */ nextBehaviorSubject(val: boolean) { this.behaviorSubject.next(val); } /* * For angular Behavior subject for a data service as a angular service often initializes * before component and behavior subject ensures that the component consuming the * service receives the last updated data even if there are no new * updates since the component's subscription to this data. * */ subscribeBehaviorSubject() { this.behaviorSubject // .first() .subscribe( val => console.log(val), err => console.error(err), () => console.log('completed') ); }
app.component.html
Subject: <button (click)="nextSubject(true)">true</button> <button (click)="nextSubject(false)">false</button> <br> BehaviorSubject: <button (click)="nextBehaviorSubject(true)">true</button> <button (click)="nextBehaviorSubject(false)">false</button>
Finally operator
usingFinallyOperator() { Observable .interval(500) .take(4) .finally(() => console.log('End of the observable, Hello World')) .subscribe( val => console.log('count taker ' + val) ); }
Stopping / Intercepting Observable
Imagine using Gmail where it allows you to undo email sent? We can produce similar experience with Observable
// subscription is created when an observable is being subscribed subscription: Subscription; // boolean variable for showing stop observable using takeWhile operator isTrue: boolean = true; /* * basic interval can be used as delay too * Imagine Gmail allows you to send and undo send within 4 seconds of sending * Use Case: Perform an action 8 seconds later then intercept if user choose to undo the action * */basicInterval() { let undoInSeconds: number = 8; this.subscription = Observable .interval(1000) .take(undoInSeconds) .takeWhile(() => this.isTrue) .subscribe( (val: number) => { console.log(`${val + 1} seconds... UNDO`); ( val === (undoInSeconds - 1) ) ? console.log('Email sent / Action performed') : null; } ); } /* * This is to stop observable from continuing performance * Use Case: Stop observable from running like how Gmail could undo email being sent * */stopObservableUsingUnsubscribe() { if (!!this.subscription) { this.subscription.unsubscribe(); console.log('subscription: Subscription is unsubscribed'); } } /* * This is also to stop observable from continuing performance * This method is more preferable than subscribing method then unsubscribe * Use Case: Stop observable from running like how Gmail could undo email being sent * */stopObservableUsingTakeWhile() { this.isTrue = false; }
Perform conditional Reactive Form validation
This is my approach to performing conditional validation when using Angular. We will minimally manipulate the Observable of RxJS in this example. Let’s try by creating or using app.component.ts.
1 . Create a form that has two form controls reason and otherReason.
/* * Refer to angular official guide at https://angular.io/guide/reactive-forms on how to create reactive form with form controls * */createForm() { this.form = this.formBuilder.group({ reason: ['', Validators.required ], otherReason: [''], }); }
2 . Create two methods addValidator and removeValidator.
/* * For conditional form validation use * */ private addValidator( control: AbstractControl, newValidator ){ let existingValidators = control.validator; control.setValidators(Validators.compose([ existingValidators, newValidator ])); control.updateValueAndValidity(); } /* * For conditional form validation use * */ private removeValidator( control: AbstractControl ){ control.clearValidators(); control.updateValueAndValidity(); }
3 . Create third method called conditionalFormValidation.
conditionalFormValidation( parentField, childField, matchValue = 'Others' ) { this.form .get(parentField) .valueChanges .forEach(( value: string ) => { const childFieldControl: AbstractControl = this.form.get(childField); if ( value === matchValue ) { this.addValidator(childFieldControl, Validators.required); } else { this.removeValidator(childFieldControl); } }); }
4 . Create or add to ngOnInit method
ngOnInit() { this.createForm(); this.conditionalFormValidation('reason', 'otherReason'); }
The outcome should illustrate that if we select ‘Others’ option in the dropdown list of reason, it should make otherReason form control field as required.
Perform manual operations after reading from Firebase database
When using AngularFire2 with Angular + Firebase, in getting a list of data from Firebase, we will get one observable instance but within that one observable is an array of N size. We can manually filter the array inside that one observable instance using arr.filter. It is different from RxJS .filter operator. Of course we can also flatten what is inside an array using .flatMap() operator. However, we’re going use JavaScript array filtering, instead of non-observable filtering, right after getting an observable object.
this.db.list(`/review`) .map(arr => arr.filter(item => item.rating > 3));
We can also reverse an array using JavaScript array reverse function. See reference. On a side note, using negative timestamp to reverse Firebase display is also another option.
this.db.list('/review') .map(arr => { return arr.reverse(); });
Besides those above, we can also use the response returned from AngularFire2 to perform “local filter/search”. This result can be valuable for autocomplete filtered list or searches. However, this approach below suffers severely in performance issue where at the magnitude of the size of the returned response from AngularFire2. E.g. if the list has N items. It has to iterate at least 1N. Perhaps an average of 2N.
getFilteredClientList(searchQuery: string): Observable<Client[]> { let query = searchQuery.trim().toLowerCase(); return this.db.list(`/client`) .map((arr: Client[]) => { return arr.filter((item: Client) => { return item.email.toLowerCase().indexOf(query) === 0 || item.given_name.toLowerCase().indexOf(query) === 0 || item.family_name.toLowerCase().indexOf(query) === 0 }); }); }