Search code examples
angularrxjsionic3switchmap

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


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


Solution

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