Trying to cancel existing http request with new one using switchMap in Ionic 3

792 Views Asked by At

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'?

2

There are 2 best solutions below

0
On BEST ANSWER

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> {
  ...
  }

}
0
On

You were very close. Please use switch map as given below. Hope this help!

this.submitStream$ = Rx.Observable.fromEvent(this.button._elementRef.nativeElement, 'click');

this.submitStream$
      .switchMap(click => {
        return this.dummyAPIResponse(); // this should return observalbe(i.e new observalbe)...
      })
      .subscribe((res) => {
        console.log(res);
      });


dummyAPIResponse(){
 return Rx.Observable.create(observer => {
      setTimeout(() => {
        observer.next([1, 2, 3, 4]);
      }, 10000);
    });
}