How to Dispatch action which depends on Http method?

304 Views Asked by At

I am using NGXS for state management in my angular application.

Which one is a good practice?

  1. First HTTP call then Dispatch action in it's subscription?
  2. First Dispatch action and then HTTP call in it's pipe method?

For example, what is the process of user login using state management? Here, I have to make an HTTP call & then I have to do something depending on the response I get from the HTTP response.

Which one is good practice & why?

Also, please share example. Thanks in advance.

1

There are 1 best solutions below

0
Joosep Parts On

Typical flow would look like so that LoginIn action, triggers HTTP request authService.loginIn() to validate credentials, successful response triggers LoginSuccess, that action sets credentials to service/storage etc (isLoggedIn = true), components/services listening to state change react (or use authService to store logIn state);

LoginComponent.ts

  login() {
    console.log(this.loginFrom.valid);
    console.log(this.loginFrom.value);
    this.store.dispatch(new Login(this.loginFrom.value));
  }

AuthService.ts

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private baseUrl = environment.apiUrl;
  constructor(private _http: HttpClient) {}

  loginIn(user: User) {
    let { email, password } = user;
    return this._http.post<User>(`${this.baseUrl}/login`, { email, password });
  }
}

Effects.ts

@Injectable()
export class AuthEffects {
  @Effect()
  LoginIn: Observable<any> = this.actions.pipe(
    ofType(AuthActionTypes.LOGIN),
    map((action: Login) => action.payload),
    switchMap((payload) => {
      return this.authService.loginIn(payload).pipe(
        map((user: any) => {
          return new LoginSuccess({ token: user.token, email: payload.email });
        }),
        catchError((error) => {
          return of(new LoginFailure({ error }));
        })
      );
    })
  );
  @Effect({ dispatch: false })
  LoginSuccess: Observable<any> = this.actions.pipe(
    ofType(AuthActionTypes.LOGIN_SUCCESS),
    tap((user) => {
      localStorage.setItem('token', user.payload.token);
      this.router.navigate(['home']);
    })
  );

  @Effect({ dispatch: false })
  LogInFailure: Observable<any> = this.actions.pipe(
    ofType(AuthActionTypes.LOGIN_ERROR)
  );

  @Effect({ dispatch: false })
  LogOut: Observable<any> = this.actions.pipe(
    ofType(AuthActionTypes.LOGOUT),
    tap(() => {
      localStorage.removeItem('token');
      this.router.navigate(['login']);
    })
  );

  constructor(
    private actions: Actions,
    private authService: AuthService,
    private router: Router
  ) {}
}

Actions.ts

export enum AuthActionTypes {
  LOGIN = '[Auth] Login',
  LOGIN_SUCCESS = '[Auth] SUCCESS',
  LOGIN_ERROR = '[Auth] LOGIN_ERROR',
  LOGOUT = '[Auth] Logout',
}

export class Login implements Action {
  type = AuthActionTypes.LOGIN;
  constructor(public payload: any) {}
}

export class LoginSuccess implements Action {
  type = AuthActionTypes.LOGIN_SUCCESS;
  constructor(public payload: any) {}
}

export class LoginFailure implements Action {
  type = AuthActionTypes.LOGIN_ERROR;
  constructor(public payload: any) {}
}

export class Logout implements Action {
  type = AuthActionTypes.LOGOUT;
}

export type All = Login | LoginSuccess | LoginFailure | Logout;

Reducer.ts

export interface IState {
  isAuthenticated: boolean;
  user: User | null;
  error: any;
}

export const initialState = {
  isAuthenticated: false,
  user: null,
  error: null,
};

export function reducer(state = initialState, action: any): IState {
  switch (action.type) {
    case AuthActionTypes.LOGIN_SUCCESS: {
      return {
        ...state,
        isAuthenticated: true,
        user: {
          email: action.payload.email,
          password: action.payload.password,
        },
        error: null,
      };
    }

    case AuthActionTypes.LOGIN_ERROR: {
      return {
        ...state,
        error: 'Invalid User/Password',
      };
    }

    case AuthActionTypes.LOGOUT: {
      return initialState;
    }

    default: {
      return state;
    }
  }
}

HomeComponent.ts

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
})
export class HomeComponent implements OnInit {
  state$: Observable<any>;
  isAuthenticated: boolean;
  error: any;
  user: User;
  constructor(private store: Store<IAppState>) {
    this.state$ = this.store.select((state) => state.auth);
  }

  ngOnInit() {
    this.state$.subscribe((r: IState) => {
      this.user = r.user;
      this.error = r.error;
      this.isAuthenticated = r.isAuthenticated;
    });
  }

  logout() {
    this.store.dispatch(new Logout());
  }
}