Jerry's Blog

Recording what I learned everyday

View on GitHub


19 July 2019

Angular (34) -- NgRx

by Jerry Zhang

LeetCode Day 14: P121. Best time to buy and sell stock I (Easy)

题目

一个数组,每个数代表每天的股票价格。只许买卖一次,求最大的利润。

Example:

Input: [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.

我的思路:

动态规划。然后就放弃了。。。

看答案

class Solution {
    public int maxProfit(int[] prices) {
       int minimumPrice = Integer.MAX_VALUE;
       int maximumProfit = 0;
       for(int i = 0; i < prices.length; i++){
            minimumPrice = Math.min(minimumPrice, prices[i]);
            maximumProfit = Math.max(maximumProfit,(prices[i] - minimumPrice));
        }
       return maximumProfit;
    }
}
class Solution {
    public int maxProfit(int[] prices) {
        int b = Integer.MIN_VALUE, s = 0;
        for (int p: prices) {
            int tb = b, ts = s;
            b = Math.max(tb, -p);
            s = Math.max(ts, tb + p);
        }
        
        return s;
    }
}

每次更新最小值,更新最大利润。

Angular: Using NgRx in Auth Module

Tip: A new Reducer needs to be added in the app.module.ts file

Create a Reducer

Create a new folder called store including reducer and actions

Auth Reducer: (The auth actions file will be created later)

import {User} from '../user.model';
import * as AuthActions from './auth.actions';

export interface State {
  user: User;
}

const initialState: State = {
  user: null
};

export function authReducer(state = initialState, action: AuthActions.AuthActions) {
  switch (action.type) {
    case AuthActions.LOGIN:
      const user = new User(
        action.payload.email,
        action.payload.userId,
        action.payload.token,
        action.payload.expirationDate);
      return {
        ...state,
        user: user
      };
    case AuthActions.LOGOUT:
      return {
        ...state,
        user: null
      };
    default: return state;
  }
}

Add the reducer in the app.module

import * as fromApp from './store/app.reducer';

StoreModule.forRoot(fromApp.appReducer),

Union Reducers

Because we have multiple reducer now, we can create a new app.reducer.ts that merges all the reducers together.

import * as fromShoppingList from '../shopping-list/store/shopping-list.reducer';
import * as fromAuth from '../auth/store/auth.reducer';
import {ActionReducerMap} from '@ngrx/store';

export interface AppState {
  shoppingList: fromShoppingList.State;
  auth: fromAuth.State;
}

export const appReducer: ActionReducerMap<AppState> = {
  shoppingList: fromShoppingList.shoppingListReducer,
  auth: fromAuth.authReducer
};

Then we need to replace all generic type of the store injection with Store<fromApp.AppState> .

Create an auth.actions.ts file.

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

export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';

export class Login implements Action {
  readonly type = LOGIN;

  constructor(
    public payload: {
      email: string;
      userId: string;
      token: string;
      expirationDate: Date;
  }) {}
}

export class Logout implements Action {
  readonly type = LOGOUT;
}

export type AuthActions = Login | Logout;

Use the store in the auth service file

Previously, we use this.user.next(loadedUser); to signal the application that we have updated the user now.

Now, we dispatch an action with NgRx store.

import * as fromApp from '../store/app.reducer';
import * as AuthActions from './store/auth.actions';

constructor(
    private http: HttpClient,
    private router: Router,
    private store: Store<fromApp.AppState>
  ) {}
  
this.store.dispatch(new AuthActions.Login({
        email: loadedUser.email,
        userId: loadedUser.id,
        token: loadedUser.token,
        expirationDate: new Date(userData._tokenExpirationDate)
      }));
logout() {
    // this.user.next(null);
    this.store.dispatch(new AuthActions.Logout());
    this.router.navigate(['/auth']);
    localStorage.removeItem('userData');
    if (this.tokenExpirationTimer) {
      clearTimeout(this.tokenExpirationTimer);
    }
    this.tokenExpirationTimer = null;
  }

Update the code that we subscribe the user with authService.

In auth-interceptor:

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {

  constructor(private authService: AuthService, private store: Store<fromApp.AppState>) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.store.select('auth').pipe(
      take(1),
      map(authState => {
        return authState.user;
      }),
      exhaustMap(user => {
        if (!user) {
          return next.handle(req);
        }
        const modifiedReq = req.clone({params: new HttpParams().set('auth', user.token)});
        return next.handle(modifiedReq);
      }));
  }
}

In Auth guard:

@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {

  constructor(
    private authService: AuthService,
    private router: Router,
    private store: Store<fromApp.AppState>) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
    Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.store.select('auth').pipe(
      take(1),
      map(authState => {
        return authState.user;
      }),
      map(user => {
      const isAuth = !!user;
      if (isAuth) {
        return true;
      }
      return this.router.createUrlTree(['/auth']);
    }));
  }
}

In the header component:

export class HeaderComponent implements OnInit, OnDestroy {
  isAuthenticated = false;
  collapsed = true;
  private userSub: Subscription;
  constructor(
    private dataStorageService: DataStorageService,
    private authService: AuthService,
    private store: Store<fromApp.AppState>
  ) { }

  ngOnInit() {
    this.userSub = this.store.select('auth')
      .pipe(map(authState => authState.user))
      .subscribe(user => {
      this.isAuthenticated = !!user;
      console.log(!user);
      console.log(!!user);
    });
  }
}

Important Note: Every Actions we dispatched always reaches ALL reducers.

Important Note: Always copy the old state, and return this state in a default case.

Because if a shopping list action is dispatched, it still reaches the auth reducer.

Important Note: The ID of an action type must be unique.

A name like export const ADD_INGREDIENT = '[Shopping List] Add Ingredient'; is recommended.

export const ADD_INGREDIENT = '[Shopping List] Add Ingredient';
export const ADD_INGREDIENTS = '[Shopping List] Add Ingredients';
export const UPDATE_INGREDIENT = '[Shopping List] Update Ingredient';
export const DELETE_INGREDIENT = '[Shopping List] Delete Ingredient';
export const START_EDIT = '[Shopping List] Start Edit';
export const STOP_EDIT = '[Shopping List] Stop Edit';
export const LOGIN = '[Auth] Login';
export const LOGOUT = '[Auth] Logout';
tags: Angular