Jerry's Blog

Recording what I learned everyday

View on GitHub


23 July 2019

Angular (38) -- NgRx Effects

by Jerry Zhang

LeetCode Day 18: P141. Linked List Cycle (Easy)

题目:

给一个linked list, 检查是否有环。要求O(1)

我的思路:

一开始想每经过一个节点,把地址记到HashSet里。可是时间复杂度会非常高。放弃了。

最优解:

参考知乎一篇文章:

https://zhuanlan.zhihu.com/p/38521018

快慢指针:首先,使用快慢指针判断链表中是否有环就好比是郭小明和博尔特赛跑,如果两人在笔直的道路上赛跑(链表不含有环), 那么从同一起点出发后,郭小明永远追不上(遇不到)博尔特;如果是类似马拉松比赛,最后是在体育馆中的环形跑道跑完(假设无限跑), 那么博尔特总会在环形跑道中“追上”郭小明。

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }

        ListNode slow = head; 
        ListNode fast = head; 

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                return true; 
            }
        }
        return false;
    }
}

Angular: NgRx Effects

Add an effect for signing up

Add a new action SIGNUP_START

export const SIGNUP_START = '[Auth] Signup Start';

export class SignupStart implements Action {
  readonly type = SIGNUP_START;

  constructor(public payload: { email: string; password: string}) {}
}

Add a new effect authSignup

@Effect()
  authSignup = this.actions$.pipe(
    ofType(AuthActions.SIGNUP_START),
    switchMap((signupAction: AuthActions.SignupStart) => {
      return this.http.post<AuthResponseData>(
        'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyCQUonbyGcWNNCk-jS2WWkgpR3U0TtkIdA',
        {
          email: signupAction.payload.email,
          password: signupAction.payload.password,
          returnSecureToken: true
        })
        .pipe(
          tap(resData => {
            this.authService.setLogoutTimer(+resData.expiresIn * 1000);
          }),
          map(resData => {
            return handleAuthentication(
              +resData.expiresIn,
              resData.email,
              resData.localId,
              resData.idToken
            );
          }),
          catchError(errorRes => {
            return handleError(errorRes);
          })
      );
    })
  );

Dispatch a new SignupStart action in auth.component.ts.

if (this.isLoginMode) {
      this.store.dispatch(
        new AuthActions.LoginStart({email: email, password: password})
      );
    } else {
      this.store.dispatch(
        new AuthActions.SignupStart({email: email, password: password})
      );
    }

Fix some bugs

In the reducer, return the same state when we login start and signup start

case AuthActions.LOGIN_START:
case AuthActions.SIGNUP_START:
  return {
    ...state,
    authError: null,
    loading: true
  };

Unsubscribe the store subscription:

In auth.component.ts:

private storeSub: Subscription;

ngOnInit(): void {
    this.storeSub = this.store.select('auth').subscribe(authState => {
      this.isLoading = authState.loading;
      this.error = authState.authError;
      if (this.error) {
        this.showErrorAlert(this.error);
      }
    });
  }
  
ngOnDestroy(): void {
    if (this.closeSub) {
      this.closeSub.unsubscribe();
    }
    if (this.storeSub) {
      this.storeSub.unsubscribe();
    }
  }

Add a new action ClearError.

export const CLEAR_ERROR = '[Auth] Clear Error';

export class ClearError implements Action {
  readonly type = CLEAR_ERROR;
}

In the reducer:

case AuthActions.CLEAR_ERROR:
      return {
        ...state,
        authError: null
      };

In auth.component.ts:

onHandleError() {
    this.store.dispatch(new AuthActions.ClearError());
  }

LocalStorage in NgRx

Create a new Action, AUTO_LOGIN

export const AUTO_LOGIN = '[Auth] Auto Login';

export class AutoLogin implements Action {
  readonly type = AUTO_LOGIN;
}

Set a user in local storage in handleAuthentication

const handleAuthentication = (
  expiresIn: number,
  email: string,
  userId: string,
  token: string
) => {
  const expirationDate = new Date(
    new Date().getTime() + expiresIn * 1000
  );
  const user = new User(email, userId, token, expirationDate);
  localStorage.setItem('userData', JSON.stringify(user));
  return new AuthActions.AuthenticateSuccess({
    email: email,
    userId: userId,
    token: token,
    expirationDate: expirationDate
  });
};

Add a new effect authLogout

@Effect({dispatch: false})
  authLogout = this.actions$.pipe(
    ofType(AuthActions.LOGOUT),
    tap(() => {
      this.authService.clearLogoutTimer();
      localStorage.removeItem('userData');
      this.router.navigate(['/auth']);
    })
  );

Add a new effect autoLogin

@Effect()
  autoLogin = this.actions$.pipe(
    ofType(AuthActions.AUTO_LOGIN),
    map(() => {
      const userData: {
        email: string;
        id: string;
        _token: string;
        _tokenExpirationDate: string;
      } = JSON.parse(localStorage.getItem('userData'));
      if (!userData) {
        return { type: 'DUMMY'};
      }

      const loadedUser = new User(
        userData.email,
        userData.id,
        userData._token,
        new Date(userData._tokenExpirationDate)
      );

      if (loadedUser.token) {
        // this.user.next(loadedUser);
        const expirationDuration = new Date(userData._tokenExpirationDate).getTime() - new Date().getTime();
        this.authService.setLogoutTimer(expirationDuration);
        return new AuthActions.AuthenticateSuccess({
            email: loadedUser.email,
            userId: loadedUser.id,
            token: loadedUser.token,
            expirationDate: new Date(userData._tokenExpirationDate)
        });
        // const expirationDuration = new Date(userData._tokenExpirationDate).getTime() - new Date().getTime();
        // this.autoLogout(expirationDuration);
      }
      return { type: 'DUMMY'};
    })
  );

Dispatch auto login in app component

ngOnInit(): void {
    this.store.dispatch(new AuthActions.AutoLogin());
    this.loggingService.printLog('Hello from AppComponent ngOnInit');
  }
tags: Angular