0
votes

I have a question of how to return an Observable to an outer promise. I have in my effects.ts the following:

  @Effect()
  login$: Observable<Action> = this.actions$.pipe(
    ofType(UserActions.LOGIN),
    switchMap((action: UserActions.LoginAction) => {

      this.db.auth.signInWithEmailAndPassword(action.payload.email, action.payload.password).then(data => {
        this.db.auth.currentUser.getIdToken().then(reply => {
          return this.http.post(environment.apiUrl+'/api/login', { token: reply }).subscribe(response => {
              if (response['valid'] === 'true') {
                localStorage.setItem('token', JSON.stringify(reply));
                this.router.navigate(['dash']);
              }
            });
        });
      }).catch(err => {
        console.log('signIn failed: ' + err.message);
      });
    })
  );

Now, my goal overall is to simply log in to FireBase to be able to grab an ID token and submit a post request to the back-end. If the response comes back with the property valid "true" then proceed to navigate to the "dash" module.

However, my problem is:

Argument of type '(action: UserActions.LoginAction) => void' is not assignable to parameter of type '(value: LoginAction, index: number) => ObservableInput'. Type 'void' is not assignable to type 'ObservableInput'.ts(2345)

I believe it is because I put my return statement within an outer promise. But I don't know how to make it return properly because Firebase uses promises, and NgRX uses Observables! I am trying to return an Observable from within a promise... thank you

3
firstly there are promises and secondly you do not return an actionChris

3 Answers

1
votes

You must return an action after calling api

  @Effect()
  login$: Observable<Action> = this.actions$.pipe(
    ofType(UserActions.LOGIN),
    switchMap((action: UserActions.LoginAction) =>
      from(this.db.auth.signInWithEmailAndPassword(action.payload.email, action.payload.password)),
    ),
    switchMapTo(from(this.db.auth.currentUser.getIdToken())),
    switchMap(token => this.http.post(environment.apiUrl + '/api/login', { token })),
    tap((response: any) => {
      if (response.valid === 'true') {
        localStorage.setItem('token', JSON.stringify(token));
        this.router.navigate(['dash']);
      }
    }),
    map(response => UserActions.LoginSuccess({ response })),
    catchError(error => UserAction.LoginError({ error })),
  );
0
votes

You're definitely on the right track. I remember struggling with this type of thing when I first was learning RxJS.

A few things I want to point out:

  1. Effects are supposed to return Actions. That's what the error message is saying. If you don't want an Action to be returned, then you have to make it: @Effect({ dispatch: false }). In this type of situation, though, you should definitely dispatch an Action at the end, something like: "User Successfully Logged In" with the User Token as the payload. Then you can store that in your state using the reducer. But if all you really want to do is navigate to /dash afterwards, then use the { dispatch: false } parameter.
  2. To convert a Promise to an Observable, use the from function inside the rxjs library. So you would wrap everything up to where you'd normally stick the then function: from(this.db.auth.signInWithEmailAndPassword(action.payload.email, action.payload.password)).
  3. Avoid switchMap for this type of situation. It will lead to race conditions. Use exhaustMap instead. And then use one for each Promise. So it would end up looking something like:

    @Effect() login$: Observable<Action> = this.actions$.pipe( ofType(UserActions.LOGIN), exhaustMap((action: UserActions.LoginAction) => from(this.db.auth.signInWithEmailAndPassword(action.payload.email, action.payload.password)), exhaustMap(data => from(this.db.auth.currentUser.getIdToken()), exhaustMap(reply => { return this.http.post(environment.apiUrl+'/api/login', { token: reply }), map(response => { if (response['valid'] === 'true') { localStorage.setItem('token', JSON.stringify(reply)); this.router.navigate(['dash']); } }); }); }) }) );

0
votes
  @Effect()
  login$: Observable<Action> = this.actions$.pipe(
    ofType(UserActions.LOGIN),
    switchMap((action: UserActions.LoginAction) =>
      from(this.db.auth.signInWithEmailAndPassword(action.payload.email, action.payload.password)),
    ),
    // switchMapTo(from(this.db.auth.currentUser.getIdToken())),
    switchMapTo(from(this.db.authState.pipe(
      take(1),
      switchMap((user)=>{
        if (user)
          return from(user.getIdToken());
        else
          return of(null);
      })
    ))),
    switchMap(token => this.http.post(environment.apiUrl + '/api/login', { token: token })),
    tap((response: any) => {
      if (response.valid === 'true') {

        console.log("effects success");

        // localStorage.setItem('token', JSON.stringify(token));
        this.router.navigate(['dash']);
      }
    }),
    map(response => new UserActions.LoginSuccessAction({ response }))//,
    // catchError(error => new UserActions.LoginFailureAction({ error }))
  );

After doing some serious research, I coded the correct answer. Thanks to Chris for his contribution.