My goal is to use RxJs switchMap
in an Ionic 3 project, to cancel any ongoing login attempts if a server is offline, and use latest request only. Otherwise I can get some unwanted asynchronous side effects later.
In my view layer, I'm using submitStream$
to capture a button clickstream, then passing that to the service layer as a method argument.
In the service layer, I'm creating response$
via a switchMap
, combining, the passed parameter submitStream$
, with an Observable<Response>
that comes back from this.webServiceUtil.testLogin()
.
I tried implementing this code:
HTML markup
<button #submit type="submit">Submit</button>
Typescript : View Layer
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import { ElementRef, ViewChild } from '@angular/core';
export class LoginPage {
@ViewChild('submit') button: ElementRef
submitStream$:any;
...
ionViewDidLoad() {
this.submitStream$ =
Observable.fromEvent(this.button.nativeElement, 'click')
}
...
}
Typescript : Service Layer (submitStream$ comes in as method argument)
this.response$ = submitStream$.switchMap(click =>
this.webServiceUtil.testLogin())
I've also tried creating this.submitStream$
in ngAfterViewInit()
When things didn't work I also tried appending this to the Observable.fromEvent:
.subscribe(ev => {
console.log('LoginPage: ionViewDidLoad() submitStream$.subscribe(): ev:', ev);}
)
How do I overcome the 'ERROR TypeError: Invalid event target'?
It seems that you have to do some funky stuff to make what you subscribe to, look correct in RxJs's eyes...
So this is what I did to solve the ERROR TypeError: Invalid event target:
@ViewChild('submit') button: any;
this.submitClickStream$ =
Observable.fromEvent(this.button._elementRef.nativeElement , 'click')
.subscribe(ev => {
console.log('ev: ', ev);},
(err) => {console.error(err);}
);
In AuthService, passing either the submitClickStream$
, or the button element from @ViewChild, and doing Observable.fromEvent on it from within AuthService, I was unable to get the subscribe with the console logging of events, to ever fire.
So, I introduced a BehaviourSubject intermediary.
I could just use regular (click)='onSubmit()'
again in the <button>
element of LoginPage.html.
In the onSubmit() method of LoginPage.ts, I could directly use the BehaviourSubject with this.clickStream$.next('');
.
This did away with the complexities of Observable.fromEvent, and ViewChild.
switchMap finally worked, cancelling earlier ongoing requests without messy unsubscribe logic.
LoginPage.ts
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { AuthService } from './../../providers/auth-service';
import { LoginCredentials } from './../../model/loginCredentials';
export class LoginPage {
private clickStream$:BehaviorSubject<any>;
constructor(
private authService:AuthService,
) {
this.clickStream$ = new BehaviorSubject('');
}
onSubmit():void {
console.log('LoginPage: onSumbit()');
this.clickStream$.next('');
this.authService
.login(this.prepareCredentials(),
this.clickStream$
);
}
}
AuthService
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { LoginCredentials } from './../../model/loginCredentials';
import { WebServiceUtil } from './web-service-util';
@Injectable()
export class AuthService {
login(
loginCredentials: LoginCredentials,
clickStream$: BehaviorSubject<any>
) {
this.response$ = clickStream$.switchMap(click => {
console.log('AuthService: login() : click: ', click);
return this.webServiceUtil.testLogin();
}
);
this.response$.subscribe(
(response:Response) => {....},
(err) => {console.error(err); ....}
}
}
WebServiceUtil
@Injectable()
export class WebServiceUtil {
testLogin(
): Observable<Response> {
...
}
}