0
votes

I am receiving an error I can't understand the cause.
I've read everything I could on effect creation, but still can't figure this error cause.
I'm new to angular and especially Ngrx, so maybe I don't see something obvious.
I've created an action:

export interface ServerAuthData{
  AccessToken: string;
  RefreshToken: string;
  Utente: Utente;
}
export interface AuthState {
  isAuthenticated: boolean;
  errorMessage: string;
  accessToken: string;
  refreshToken: string;
  utente: Utente;
}

    export const authLogin = createAction(
    '[Auth] Login',  
    props<{ username: string,password: string }>()
    );

its reducer:

const reducer = createReducer(
  initialState,
  on(authLogin, (state) => ({ ...state })),
  on(authLogout, (state) => (
    { ...state,
      isAuthenticated: false ,
      utente: null,
      accessToken: "",
      refreshToken: "",
    })),
  on(authLoginSuccess, (state,{authState}) => (
    { ...state,
      isAuthenticated: true,
      utente: authState.utente,
      accessToken: authState.accessToken,
      refreshToken: authState.refreshToken,
      erroMessage: authState.errorMessage,
    }))
);

the service I'm going to call to login the user:

  login(username: string,password: string) {
    var formData: any = new FormData();
    formData.append("username", username);
    formData.append("password", password);
    console.log("Chiamo il server");
    return this.http.post<ServerAuthData>(this.loginUrl, formData);
  }

and finally the effect:

  login$ = 
  createEffect(() =>
      this.actions$.pipe(
        ofType(authLogin),
        switchMap((action) =>
          this.authService.login(action.username,action.password).pipe(
            tap((serverAuthData) => console.log('Dopo login. Ricevuto:' + serverAuthData)),
            map((serverAuthData) =>  
              console.log("Converting data");
              authLoginSuccess({authState:{
                isAuthenticated: true,
                accessToken: serverAuthData.AccessToken,
                errorMessage: "",
                refreshToken : serverAuthData.RefreshToken,
                utente: serverAuthData.Utente,
              }}
              
              )),
            catchError((error: any) => of(this.authService.loginFailure(error)))
          )
        )
      )
      
  );

Everything worked before using map instead of switchMap,and with {dispatch: false} but of course no new action was dispatched. Switching to switchMap and removing {dispatch: false}has generate the error:

Type 'Observable<void | ({ authState: AuthState; } & TypedAction<"[Auth] LoginSuccess">)>' is not assignable to type 'EffectResult'.

Type 'Observable<void | ({ authState: AuthState; } & TypedAction<"[Auth] LoginSuccess">)>' is not assignable to type 'Observable'.

Type 'void | ({ authState: AuthState; } & TypedAction<"[Auth] LoginSuccess">)' is not assignable to type 'Action'.

Type 'void' is not assignable to type 'Action'.

Can someone help me?
EDIT: I understand that switchMap HAS to return a value. Doesn't dispatching a new action returns a value? I've also tried return authLoginSuccess but no way.

2

2 Answers

0
votes

I guess it's because you are not returning anything in your mergeMap.

Because you didn't use {} like: map(serverAuthData => { ... return X}). you are allowed to write only one line which in this case is your console.log("Converting data"); and it returns undefined.

so you can remove console.log("Converting data"); and let the authLoginSuccess be the one or you can use { } and return the one you want.

like this:

you can also import Effect from import { Effect } from "@ngrx/effects";

and write createEffect as a decorator.

  @Effect()
  login$ = this.actions$.pipe(
    ofType(authLogin),
    switchMap(action =>
      this.authService.login(action.username, action.password).pipe(
        tap(serverAuthData =>
          console.log("Dopo login. Ricevuto:" + serverAuthData)
        ),
        map(serverAuthData => {
          console.log("Converting data");
          return authLoginSuccess({
            authState: {
              isAuthenticated: true,
              accessToken: serverAuthData.AccessToken,
              errorMessage: "",
              refreshToken: serverAuthData.RefreshToken,
              utente: serverAuthData.Utente
            }
          });
        }),
        catchError((error: any) => of(this.authService.loginFailure(error)))
      )
    )
  );
0
votes

SOLUTION AT THE END OF THE POST.
To better clarify I have further simplified the code to be smaller :
Action:

export const authLogin = createAction(
    '[Auth] Login',  
    props<{ username: string,password: string }>()
    );
export const authLoginSuccess = createAction('[Auth] LoginSuccess',
    props<{serverAuthData: ServerAuthData}>()
    );

Reducer:

  on(authLoginSuccess, (state,{serverAuthData}) => (
    { ...state,
      isAuthenticated: true,
      utente: serverAuthData.Utente,
      accessToken: serverAuthData.AccessToken,
      refreshToken: serverAuthData.RefreshToken,
      erroMessage: "",
    }))
);

and finally effect:

@Injectable()
export class AuthEffects {
  login$ = 
  createEffect(() =>
      this.actions$.pipe(
        ofType(authLogin),
        switchMap((action) =>
          this.authService.login(action.username,action.password).pipe(
//            tap((serverAuthData) => console.log('Dopo login. Ricevuto:' + serverAuthData)),
            map((serverAuthData) =>  authLoginSuccess({serverAuthData})),
            catchError((error: any) => of(this.authService.loginFailure(error))),
          )
        )
      )
  );

I still receive an error:

Type 'Observable<void | ({ serverAuthData: ServerAuthData; } & TypedAction<"[Auth] LoginSuccess">)>' is not assignable to type 'EffectResult'.
Type 'Observable<void | ({ serverAuthData: ServerAuthData; } & TypedAction<"[Auth] LoginSuccess">)>' is not assignable to type 'Observable'.
Type 'void | ({ serverAuthData: ServerAuthData; } & TypedAction<"[Auth] LoginSuccess">)' is not assignable to type 'Action'.
Type 'void' is not assignable to type 'Action'.

I can't understand what the problem is.
I also tried with @effect decorator, but error moved from design to runtime.
EDIT: removing CreateEffect and using @Effect() works fine!!!

  //login$ = 
  //createEffect(() => 
  @Effect()
     login$= this.actions$.pipe(
        ofType(authLogin),
        switchMap((action) =>
          this.authService.login(action.username,action.password).pipe(
            tap((serverAuthData) => console.log('Dopo login. Ricevuto:' + serverAuthData)),
            map(serverAuthData =>  authLoginSuccess({serverAuthData})),
            catchError((error: any) => of(this.authService.loginFailure(error))),
          )
        )
      );
  //);

FINAL EDIT: I opened a bug on GitHub, and there I received an explanation of the error.

The return type of authService.loginFailure() method seems to be void. You must return an action instead.

It works with @Effect() because decorators are not type safe. So, you need to pass the action here:
catchError(error => of(loginFailure({ error }) /* <- action */)) Adding a return value solved the issue.
I suppose these are the quirks of functional programming. I got the error on this.Actions$.pipe, of course, and focused on the success part, and I didn't realize it was loginError being void. I'm still new to Angular and ngrx.