20 July 2019
Angular (35) -- NgRx Effects
by Jerry Zhang
LeetCode Day 15: P122. Best time to buy and sell stock II (Easy)
题目:
一个数组,每个数代表每天的股票价格。可以买卖无限次,求最大的利润。
我的思路:
买价初始化为第一天的价格,利润初始化为0。
遍历整个数组,如果当前价格低于买价,就更新买价; 如果当前价格高于买价,就把差额累加到利润中,然后更新买价为当前价格(因为已经结算了,前面那个买价已经没用了)。
我的代码:
public class E_122_BestTimeToBuyAndSellStock2 {
public static int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int buy = prices[0];
int profit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < buy) {
buy = prices[i];
} else {
profit += prices[i] - buy;
buy = prices[i];
}
}
return profit;
}
public static void main(String[] args) {
int[] prices = {1,2,3,4,5};
System.out.println(maxProfit(prices));
}
}
击败100%
坑:
- 一开始没更新买价,debug发现结果不正确。
- 一开始没考虑到空数组的情况,第一次提交失败了。
最优解:
class Solution {
public int maxProfit(int[] prices) {
int profit = 0, purchasePrice = Integer.MAX_VALUE;
for (int currentPrice : prices) {
if (currentPrice > purchasePrice) {
profit += (currentPrice - purchasePrice);
purchasePrice = Integer.MAX_VALUE;
}
purchasePrice = currentPrice;
}
return profit;
}
}
跟我的差不多,只不过把买价初始化为了最大的整数。因为无论如果都要更新买价,所以他索性把更新买价放到了if外面。 因为我初始化为prices[0],所以空数组时出了bug。他初始化为最大整数就没有这个问题了。
Angular: NgRx Effects
What are side effects?
Side effects are some logic that are not so important for the immediate update of the current state. For example, a http request, the result of that matters, it decides whether we did successfully create a new user or not. But for this process, where we start the sign-up process, this is not important.
Another example is local storage. This actually does not happen asynchronously. Accessing local storage happens synchronously so it’s done instantly and we could therefore do it in the reducer, only async code is forbidden in there.
Install NgRx effects
npm install --save @ngrx/effects
Create a new effect
Create a new file, auth.effects.ts
. Import Actions from @ngrx/effects.
constructor(
private actions$: Actions,
private http: HttpClient,
private router: Router,
private authService: AuthService
) {}
@Effect()
authLogin = this.actions$.pipe(
ofType(AuthActions.LOGIN_START),
switchMap((authData: AuthActions.LoginStart) => {
return this.http.post<AuthResponseData>(
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyCQUonbyGcWNNCk-jS2WWkgpR3U0TtkIdA',
{
email: authData.payload.email,
password: authData.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);
}) );
}),
);
Actions
is a big observable that will give you access to all dispatched actions. Here in the effect class, we do not
change any state but we can execute any other code when such an action is dispatched.
Do not use subscribe. Use pipe instead.
ofType
defines a filter for which types of effects you want to continue in this observable pipe you are creating.
New action
Because we want to send http requests from auth effect, we need a new identifier called LOGIN_START
. This is the
point that we start sending our http request.
Then, in the effect class, we can pass in this LOGIN_START
so that only this action will trigger this effect,
authLogin
.
The LOGIN_START
action should yield us some specific information that we need for logging in. So we need to create
a new class in auth.actions.ts
.
export class LoginStart implements Action {
readonly type = LOGIN_START;
constructor(public payload: {email: string; password: string}) {}
}
Continue on effect method
First step is filtering with ofType
, the second step is switchMap
. switchMap
allows us to create a new
observable by taking another observable’s data. The old observable is AuthActions.LoginStart
, the new observable is
a http request.
We need to use @Effect()
to tell Angular that this is an effect.
Previously, we catch error and then the whole observable die. But in the effect, this observable must never die.
Therefore, we must catch the error inside the switchMap.
Important: In the catchError method, we must return a non-error observable.
We use of()
to return a new observable/action. We use two helper method handleAuthentication and handleError.
This is an error of this course. He will change this later. When using handleError, we do need the of()
operator.
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
});
};
const handleError = (errorRes: any) => {
let errorMessage = 'An unknown error occured!';
if (!errorRes.error || !errorRes.error.error) {
return of(new AuthActions.AuthenticateFail(errorMessage));
}
switch (errorRes.error.error.message) {
case 'EMAIL_EXISTS':
errorMessage = 'This email exists already';
break;
case 'EMAIL_NOT_FOUND':
errorMessage = 'This email does not exist';
break;
case 'INVALID_PASSWORD':
errorMessage = 'This password is not correct.';
break;
}
return of(new AuthActions.AuthenticateFail(errorMessage));
};
Wire everything up
@Injectable
In app module, import EffectsModule. Pass in an array of all effects.
EffectsModule.forRoot([AuthEffects]),
Dispatch a login action
In auth.component:
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private store: Store<fromApp.AppState>
) {}
onSubmit(f: NgForm) {
if (!f.valid) {
return;
}
const email = f.value.email;
const password = f.value.password;
if (this.isLoginMode) {
this.store.dispatch(
new AuthActions.LoginStart({email: email, password: password})
);
} else {
this.store.dispatch(
new AuthActions.SignupStart({email: email, password: password})
);
}
f.reset();
}