NgRx (#2)- Sample về NgRx- Viết chức năng login

Flow xử lý

Chúng ta sẽ xây dựng xây dựng chức năng login với flow cơ bản sau:

  • Nhập email – password để login
  • Login thành công hiện thông báo thành công , chuyển tới trang home và hiển thị username
  • Login thất bại hiện thông báo thất bại

Thực hiện

Đầu tiên, cần tạo folder store, folder store này sẽ là nơi chứa các Entity (thực thể), với sample của chúng ta thì Entity sẽ là User. Mỗi Entity này sẽ gồm 4 thành phần chính:

  • Actions
  • State
  • Reducer
  • Selector

Actions- user.actions.ts

Tại file này chúng ta cần định nghĩa các Action

import { Action } from '@ngrx/store';

// định nghĩa type cho user action
export enum EUserActions {
    LOGIN = '[USER] Login',
    LOGIN_SUCCESS = '[USER] Login Success',
    LOGIN_FAIL = '[USER] Login Fail'
}
export class Login implements Action {
    public readonly type = EUserActions.LOGIN;
    constructor(public payload: { email: string, password: string }) { }
}
export class LoginSuccess implements Action {
    public readonly type = EUserActions.LOGIN_SUCCESS;
    constructor(public payload: string) { }
}
export class LoginFail implements Action {
    public readonly type = EUserActions.LOGIN_FAIL;
    constructor() { }
}
export type UserActions = Login | LoginSuccess | LoginFail;

State- user.states.ts

Tại file này chúng ta định nghĩa state được lưu trong store

export interface IUserLoginState {
    loading: boolean;
    success: boolean;
    fail: boolean;
    userName: string;
}
export interface IUserState {
    login: IUserLoginState;
}

Reducer- user.reducer.ts

Tại đây, dữ liệu sẽ được xử lý trước khi được “Đẩy” vào store

import { UserActions, EUserActions } from './user.actions';
import { IUserState, IUserLoginState } from './user.states';
const initLoginState: IUserLoginState = {
    loading: false,
    success: false,
    fail: false,
    userName: ''
};
const initUserState: IUserState = {
    login: initLoginState
};

export function userReducer(state = initUserState, action: UserActions): IUserState {
    switch (action.type) {
        case EUserActions.LOGIN:
            return {
>                 ...state,
                login: { ...initLoginState, loading: true}
            };
        case EUserActions.LOGIN_SUCCESS:
            return {
                ...state,
                login: { ...state.login, loading: false, success: true, userName: action.payload  }
            };
        case EUserActions.LOGIN_FAIL:
            return {
                ...state,
                login: { ...state.login, loading: false, fail: true }
            };
        default:
            return state;
    }
}

/store/index.ts

Ok, bây giờ để Reducer hoạt động chúng ta cần “Register” Reducer với Store

import * as fromUser from './user';
import { ActionReducerMap } from '@ngrx/store';

export interface IAppState {
    user: fromUser.IUserState;
}

export const appReducer: ActionReducerMap<IAppState> = {
    user: fromUser.userReducer
};

app.module.ts

Cần cập nhật lại app module

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { LoginComponent } from './login/login.component';
import { HomeComponent } from './home/home.component';
import { HttpClientModule } from '@angular/common/http';

import { appReducer } from './store';
import { EffectsModule } from '@ngrx/effects';
import { AppEffects } from './effects';
import { StoreModule } from '@ngrx/store';


@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    FormsModule,
    ReactiveFormsModule,
    StoreModule.forRoot(appReducer),
    EffectsModule.forRoot(AppEffects)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Tiếp theo chúng thực hiện việc Call Api để login. Ngrx cung cấp 1 package rất hay để xử lý việc này là NgxEffect. Tương tự với store chúng ta tạo folder effects

Effects

user.effects.ts

import { Injectable } from "@angular/core";
import { Effect, Actions, ofType } from '@ngrx/effects';
import { switchMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { UserService } from '../user.service';

import * as fromUser from '../store/user';

@Injectable()
export class UserEffects {
    constructor(private actions: Actions, private userService: UserService) {

    }
    @Effect()
    login$ = this.actions.pipe(
        ofType<fromUser.Login>(fromUser.EUserActions.LOGIN),
        switchMap(action => {
            const { email, password } = action.payload;
            return this.userService.login(email, password).pipe(
                map(res => new fromUser.LoginSuccess(email)),
                catchError(e => of(new fromUser.LoginFail()))
            );
        })
    );
};

./effects/index.ts

import { UserEffects } from './user.effects';

export const AppEffects = [UserEffects];

Components

login.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import * as fromRoot from '../store/index';
import * as fromUser from '../store/user';
import { Store } from '@ngrx/store';
import { Observable, Subject } from 'rxjs';
import { takeUntil, filter, delay } from 'rxjs/operators';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit, OnDestroy {
  loginForm: FormGroup;

  loading$: Observable<boolean>;
  success$: Observable<boolean>;
  error$: Observable<boolean>;
  destroy$: Subject<void> = new Subject();
  constructor(
    private fb: FormBuilder,
    private store: Store<fromRoot.IAppState>,
    private router: Router
  ) { }

  ngOnInit() {
    this.loading$ = this.store.select(fromUser.getLoadingLogin).pipe(takeUntil(this.destroy$));
    this.success$ = this.store.select(fromUser.getSuccessLogin).pipe(takeUntil(this.destroy$));
    this.error$ = this.store.select(fromUser.getFailLogin).pipe(takeUntil(this.destroy$));
    this.initForm();
    this.onLoginSucess();
  }
  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
  initForm() {
    this.loginForm = this.fb.group({
      email: '',
      password: ''
    });
  }
  submit() {
    const { email, password } = this.loginForm.value;
    this.store.dispatch(new fromUser.Login({ email, password }));
  }
  onLoginSucess() {
    this.success$.pipe(
      filter(success => success),
      //đợi 3s sau khi login thành công,chuyển tới home page
      delay(3000),
    ).subscribe(success => {
      this.router.navigate(['home']);
    });
  }
}

login.component.html

<h3 class="text-center">Login Page</h3>
<div class="alert alert-danger" *ngIf="error$ | async">
    Login fail
</div>
<div class="alert alert-success" *ngIf="success$ | async">
    Login success
</div>
<div class="login-form">
    <form (submit)="submit()" [formGroup]="loginForm">
        <div class="form-group">
          <label for="username">Username</label>
          <input class="form-control" id="username" placeholder="Enter email" formControlName="email">
        </div>
        <div class="form-group">
          <label for="password">Password</label>
          <input type="password" class="form-control" id="password" placeholder="Password" formControlName="password">
        </div>
        <div class="text-center">
            <div class="spinner-border" role="status" *ngIf="loading$ | async">
                <span class="sr-only">Loading...</span>
              </div>
        </div>
        <button type="submit" class="btn btn-primary" [disabled]="loading$ | async">Login</button>
      </form>
</div>

Kết quả

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *