0

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.

Abir Mahmud
  • 96
  • 1
  • 7
  • It's better to focus on one question and try to add what did you try until now, with some code explaining that. – Amer Aug 06 '22 at 08:13
  • 1
    You can check the NGXS docs, which explains how to do that with examples: https://www.ngxs.io/advanced/actions-life-cycle – Amer Aug 06 '22 at 08:14
  • Personally, I prefer to use NGXS with a facade pattern: Basically all "actions" on my store needs to be done on a service, the service will call my web service, and will dispatch actions. I prefer this because I don't like the idea that my state management library is responsible to call to the backend. – J4N Aug 22 '22 at 05:27

1 Answers1

1

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());
  }
}
Joosep Parts
  • 5,372
  • 2
  • 8
  • 33