소스 검색

Merge branch 'ngrx'

tags/before_ssr
Nils Dittberner 8 년 전
부모
커밋
e9aba1d7f3
34개의 변경된 파일591개의 추가작업 그리고 301개의 파일을 삭제
  1. +20
    -0
      package-lock.json
  2. +4
    -0
      package.json
  3. +12
    -1
      src/app/app.module.ts
  4. +11
    -3
      src/app/auth/auth-guard.service.ts
  5. +0
    -50
      src/app/auth/auth.service.ts
  6. +40
    -0
      src/app/auth/ngrx/auth.actions.ts
  7. +62
    -0
      src/app/auth/ngrx/auth.effects.ts
  8. +35
    -0
      src/app/auth/ngrx/auth.reducers.ts
  9. +6
    -3
      src/app/auth/signin/signin.component.ts
  10. +6
    -3
      src/app/auth/signup/signup.component.ts
  11. +0
    -8
      src/app/core/core.module.ts
  12. +3
    -3
      src/app/core/header/header.component.html
  13. +18
    -16
      src/app/core/header/header.component.ts
  14. +14
    -0
      src/app/ngrx/app.reducers.ts
  15. +43
    -0
      src/app/recipes/ngrx/recipe.actions.ts
  16. +39
    -0
      src/app/recipes/ngrx/recipe.effects.ts
  17. +52
    -0
      src/app/recipes/ngrx/recipe.reducers.ts
  18. +4
    -4
      src/app/recipes/recipe-detail/recipe-detail.component.html
  19. +18
    -8
      src/app/recipes/recipe-detail/recipe-detail.component.ts
  20. +24
    -23
      src/app/recipes/recipe-edit/recipe-edit.component.ts
  21. +1
    -1
      src/app/recipes/recipe-list/recipe-list.component.html
  22. +10
    -21
      src/app/recipes/recipe-list/recipe-list.component.ts
  23. +0
    -46
      src/app/recipes/recipe.service.ts
  24. +0
    -2
      src/app/recipes/recipes.component.ts
  25. +7
    -2
      src/app/recipes/recipes.module.ts
  26. +13
    -6
      src/app/shared/auth.interceptor.ts
  27. +0
    -29
      src/app/shared/data-storage.service.ts
  28. +2
    -2
      src/app/shared/logging.interceptor.ts
  29. +46
    -0
      src/app/shopping-list/ngrx/shopping-list.actions.ts
  30. +70
    -0
      src/app/shopping-list/ngrx/shopping-list.reducers.ts
  31. +20
    -15
      src/app/shopping-list/shopping-edit/shopping-edit.component.ts
  32. +1
    -1
      src/app/shopping-list/shopping-list.component.html
  33. +10
    -18
      src/app/shopping-list/shopping-list.component.ts
  34. +0
    -36
      src/app/shopping-list/shopping-list.service.ts

+ 20
- 0
package-lock.json 파일 보기

@@ -336,6 +336,26 @@
"resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.4.tgz",
"integrity": "sha512-jEpglcwMlwdXc/JgvJaJtCSkPMktnFeI0gAZxPrmbJxKVzMZJ2zM582NbW/r6M22pSdNWjcWeg1I2LRg3jQGQA=="
},
"@ngrx/effects": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-4.1.1.tgz",
"integrity": "sha1-y3WLhSeWSyWOpBlR9ZqhROPvn64="
},
"@ngrx/router-store": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-4.1.1.tgz",
"integrity": "sha1-F/rHwPX/3e+LdemnTtLLCQdPO8o="
},
"@ngrx/store": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-4.1.1.tgz",
"integrity": "sha1-aA403yd16IUnVO13f/rJW9gbfeA="
},
"@ngrx/store-devtools": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-4.1.1.tgz",
"integrity": "sha1-IHRcOcdWD9wF+k8iY4RCp+x91nY="
},
"@ngtools/json-schema": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz",


+ 4
- 0
package.json 파일 보기

@@ -21,6 +21,10 @@
"@angular/platform-browser": "^5.0.0",
"@angular/platform-browser-dynamic": "^5.0.0",
"@angular/router": "^5.0.0",
"@ngrx/effects": "^4.1.1",
"@ngrx/router-store": "^4.1.1",
"@ngrx/store": "^4.1.1",
"@ngrx/store-devtools": "^4.1.1",
"bootstrap": "^3.3.7",
"core-js": "^2.4.1",
"firebase": "^4.6.2",


+ 12
- 1
src/app/app.module.ts 파일 보기

@@ -1,6 +1,11 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from '../environments/environment';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
@@ -8,6 +13,8 @@ import { SharedModule } from './shared/shared.module';
import { ShoppingListModule } from './shopping-list/shopping-list.module';
import { AuthModule } from './auth/auth.module';
import { CoreModule } from './core/core.module';
import { appReducers } from './ngrx/app.reducers';
import { AuthEffects } from './auth/ngrx/auth.effects';

@NgModule({
declarations: [
@@ -20,7 +27,11 @@ import { CoreModule } from './core/core.module';
SharedModule,
ShoppingListModule,
AuthModule,
CoreModule
CoreModule,
StoreModule.forRoot(appReducers),
EffectsModule.forRoot([AuthEffects]),
StoreRouterConnectingModule,
!environment.production ? StoreDevtoolsModule.instrument() : []
],
bootstrap: [AppComponent]
})


+ 11
- 3
src/app/auth/auth-guard.service.ts 파일 보기

@@ -1,14 +1,22 @@
import { CanActivate } from "@angular/router";
import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router/src/router_state";
import { Injectable } from "@angular/core";
import { AuthService } from "./auth.service";
import { Store } from "@ngrx/store";
import "rxjs/add/operator/map";

import * as fromApp from '../ngrx/app.reducers';
import * as fromAuth from './ngrx/auth.reducers';

@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
constructor(private store: Store<fromApp.AppState>) {}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.authService.isAuthenticated();
return this.store.select('auth')
.take(1)
.map((authState: fromAuth.State) => {
return authState.authenticated;
});
}
}

+ 0
- 50
src/app/auth/auth.service.ts 파일 보기

@@ -1,50 +0,0 @@
import * as firebase from 'firebase';
import { Router } from '@angular/router';
import { Injectable } from '@angular/core';

@Injectable()
export class AuthService {
token: string;

constructor(private router: Router) {}

signupUser(email: string, password: string) {
firebase.auth().createUserWithEmailAndPassword(email, password).catch(
error => console.log(error)
);
}

signinUser(email: string, password: string) {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(
response => {
this.router.navigate(['/']);
firebase.auth().currentUser.getIdToken()
.then(
(token: string) => this.token = token
);
}
)
.catch(
error => console.log(error)
);
}

logout() {
firebase.auth().signOut();
this.token = null;
this.router.navigate(['/']);
}

getToken() {
firebase.auth().currentUser.getIdToken()
.then(
(token: string) => this.token = token
);
return this.token;
}

isAuthenticated() {
return this.token != null;
}
}

+ 40
- 0
src/app/auth/ngrx/auth.actions.ts 파일 보기

@@ -0,0 +1,40 @@
import { Action } from '@ngrx/store';

export const TRY_SIGNUP = 'TRY_SIGNUP';
export const SIGNUP = 'SIGNUP';
export const SIGNIN = 'SIGNIN';
export const TRY_SIGNIN = 'TRY_SIGNIN';
export const LOGOUT = 'LOGOUT';
export const SET_TOKEN = 'SET_TOKEN';

export class TrySignup implements Action {
readonly type = TRY_SIGNUP;

constructor(public payload: {username: string, password: string}) {}
}

export class Signup implements Action {
readonly type = SIGNUP;
}

export class TrySignin implements Action {
readonly type = TRY_SIGNIN;

constructor(public payload: {username: string, password: string}) {}
}

export class Signin implements Action {
readonly type = SIGNIN;
}

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

export class SetToken implements Action {
readonly type = SET_TOKEN;

constructor(public payload: string) {}
}

export type AuthActions = TrySignup | Signup | TrySignin | Signin | Logout | SetToken;

+ 62
- 0
src/app/auth/ngrx/auth.effects.ts 파일 보기

@@ -0,0 +1,62 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Actions, Effect } from '@ngrx/effects';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/mergeMap';
import { fromPromise } from 'rxjs/observable/fromPromise';
import * as firebase from 'firebase';

import * as AuthActions from './auth.actions';
import * as fromAuth from './auth.reducers';

@Injectable()
export class AuthEffects {
@Effect()
authSignup = this.actions$.ofType(AuthActions.TRY_SIGNUP)
.map((action: AuthActions.TrySignup) => {
return action.payload;
})
.switchMap((authData: {username: string, password: string}) => {
return fromPromise(firebase.auth().createUserWithEmailAndPassword(authData.username, authData.password));
})
.switchMap(() => {
return fromPromise(firebase.auth().currentUser.getIdToken());
})
.mergeMap((token: string) => {
this.router.navigate(['/']);
return [
{ type: AuthActions.SIGNUP },
{ type: AuthActions.SET_TOKEN, payload: token }
];
});

@Effect()
authSignin = this.actions$.ofType(AuthActions.TRY_SIGNIN)
.map((action: AuthActions.TrySignup) => {
return action.payload;
})
.switchMap((authData: {username: string, password: string}) => {
return fromPromise(firebase.auth().signInWithEmailAndPassword(authData.username, authData.password));
})
.switchMap(() => {
return fromPromise(firebase.auth().currentUser.getIdToken());
})
.mergeMap((token: string) => {
this.router.navigate(['/']);
return [
{ type: AuthActions.SIGNIN },
{ type: AuthActions.SET_TOKEN, payload: token }
]
});

@Effect({dispatch: false})
authLogout = this.actions$.ofType(AuthActions.LOGOUT)
.do(() => {
this.router.navigate(['/']);
});

// variable with dollar sign at the end marks an observable
constructor(private router: Router, private actions$: Actions) {}
}

+ 35
- 0
src/app/auth/ngrx/auth.reducers.ts 파일 보기

@@ -0,0 +1,35 @@
import * as AuthActions from './auth.actions';

export interface State {
token: string;
authenticated: boolean;
}

const initialState: State = {
token: null,
authenticated: false
};

export function authReducers(state = initialState, action: AuthActions.AuthActions) {
switch (action.type) {
case AuthActions.SIGNUP:
case AuthActions.SIGNIN:
return {
...state,
authenticated: true
};
case AuthActions.LOGOUT:
return {
...state,
token: null,
authenticated: false
};
case AuthActions.SET_TOKEN:
return {
...state,
token: action.payload
};
default:
return state;
}
}

+ 6
- 3
src/app/auth/signin/signin.component.ts 파일 보기

@@ -1,6 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { AuthService } from '../auth.service';
import { Store } from '@ngrx/store';

import * as fromApp from '../../ngrx/app.reducers';
import * as AuthActions from '../ngrx/auth.actions';

@Component({
selector: 'app-signin',
@@ -9,7 +12,7 @@ import { AuthService } from '../auth.service';
})
export class SigninComponent implements OnInit {

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

ngOnInit() {
}
@@ -17,7 +20,7 @@ export class SigninComponent implements OnInit {
onSignin(form: NgForm) {
const email = form.value.email;
const password = form.value.password;
this.authService.signinUser(email, password);
this.store.dispatch(new AuthActions.TrySignin({username: email, password: password}));
}

}

+ 6
- 3
src/app/auth/signup/signup.component.ts 파일 보기

@@ -1,6 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { AuthService } from '../auth.service';
import { Store } from '@ngrx/store';

import * as fromApp from '../../ngrx/app.reducers';
import * as AuthActions from '../ngrx/auth.actions';

@Component({
selector: 'app-signup',
@@ -9,7 +12,7 @@ import { AuthService } from '../auth.service';
})
export class SignupComponent implements OnInit {

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

ngOnInit() {
}
@@ -17,7 +20,7 @@ export class SignupComponent implements OnInit {
onSignup(form: NgForm) {
const email = form.value.email;
const password = form.value.password;
this.authService.signupUser(email, password);
this.store.dispatch(new AuthActions.TrySignup({username: email, password: password}));
}

}

+ 0
- 8
src/app/core/core.module.ts 파일 보기

@@ -5,10 +5,6 @@ import { HeaderComponent } from "./header/header.component";
import { HomeComponent } from "./home/home.component";
import { SharedModule } from "../shared/shared.module";
import { AppRoutingModule } from "../app-routing.module";
import { ShoppingListService } from "../shopping-list/shopping-list.service";
import { RecipeService } from "../recipes/recipe.service";
import { DataStorageService } from "../shared/data-storage.service";
import { AuthService } from "../auth/auth.service";
import { AuthInterceptor } from "../shared/auth.interceptor";
import { LoggingInterceptor } from "../shared/logging.interceptor";

@@ -26,10 +22,6 @@ import { LoggingInterceptor } from "../shared/logging.interceptor";
HeaderComponent
],
providers: [
ShoppingListService,
RecipeService,
DataStorageService,
AuthService,
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true}
],


+ 3
- 3
src/app/core/header/header.component.html 파일 보기

@@ -10,12 +10,12 @@
<li routerLinkActive="active"><a routerLink="/shopping-list">Shopping List</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<ng-template [ngIf]="!isAuthenticated()">
<ng-template [ngIf]="!(authState | async).authenticated">
<li><a routerLink="/signup">Register</a></li>
<li><a routerLink="/signin">Login</a></li>
</ng-template>
<li><a style="cursor: pointer;" (click)="onLogout()" *ngIf="isAuthenticated()">Logout</a></li>
<li class="dropdown" appDropdown *ngIf="isAuthenticated()">
<li><a style="cursor: pointer;" (click)="onLogout()" *ngIf="(authState | async).authenticated">Logout</a></li>
<li class="dropdown" appDropdown *ngIf="(authState | async).authenticated">
<a style="cursor: pointer;" class="dropdown-toggle" role="button">Manage <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a style="cursor: pointer;" (click)="onSaveData()">Save</a></li>


+ 18
- 16
src/app/core/header/header.component.ts 파일 보기

@@ -1,32 +1,34 @@
import { Component } from "@angular/core";
import { DataStorageService } from "../../shared/data-storage.service";
import { AuthService } from "../../auth/auth.service";
import { Component, OnInit } from "@angular/core";
import { Store } from "@ngrx/store";
import { Observable } from "rxjs/Observable";

import * as fromApp from '../../ngrx/app.reducers';
import * as fromAuth from '../../auth/ngrx/auth.reducers';
import * as AuthActions from '../../auth/ngrx/auth.actions';
import * as RecipeActions from '../../recipes/ngrx/recipe.actions';

@Component({
selector: 'app-header',
templateUrl: './header.component.html'
})
export class HeaderComponent {
export class HeaderComponent implements OnInit {
authState: Observable<fromAuth.State>;

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

constructor(private dataStorageService: DataStorageService, private authService: AuthService) {}
ngOnInit() {
this.authState = this.store.select('auth')
}

onSaveData() {
this.dataStorageService.storeRecipes().subscribe(
response => {
// console.log(response);
}
);
this.store.dispatch(new RecipeActions.StoreRecipes());
}

onFetchData() {
this.dataStorageService.fetchRecipes();
this.store.dispatch(new RecipeActions.FetchRecipes());
}

onLogout() {
this.authService.logout();
}

isAuthenticated() {
return this.authService.isAuthenticated();
this.store.dispatch(new AuthActions.Logout());
}
}

+ 14
- 0
src/app/ngrx/app.reducers.ts 파일 보기

@@ -0,0 +1,14 @@
import { ActionReducerMap } from '@ngrx/store/src/models';

import * as fromShoppingList from '../shopping-list/ngrx/shopping-list.reducers'
import * as fromAuth from '../auth/ngrx/auth.reducers'

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

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

+ 43
- 0
src/app/recipes/ngrx/recipe.actions.ts 파일 보기

@@ -0,0 +1,43 @@
import { Action } from '@ngrx/store';
import { Recipe } from '../recipe.model';

export const SET_RECIPES = 'SET_RECIPES';
export const ADD_RECIPE = 'ADD_RECIPE';
export const UPDATE_RECIPE = 'UPDATE_RECIPE';
export const DELETE_RECIPE = 'DELETE_RECIPE';
export const STORE_RECIPES = 'STORE_RECIPES';
export const FETCH_RECIPES = 'FETCH_RECIPES';

export class SetRecipes implements Action {
readonly type = SET_RECIPES;

constructor(public payload: Recipe[]) {}
}

export class AddRecipe implements Action {
readonly type = ADD_RECIPE;

constructor(public payload: Recipe) {}
}

export class UpdateRecipe implements Action {
readonly type = UPDATE_RECIPE;

constructor(public payload: {index: number, newRecipe: Recipe}) {}
}

export class DeleteRecipe implements Action {
readonly type = DELETE_RECIPE;

constructor(public payload: number) {}
}

export class StoreRecipes implements Action {
readonly type = STORE_RECIPES;
}

export class FetchRecipes implements Action {
readonly type = FETCH_RECIPES;
}

export type RecipeActions = SetRecipes | AddRecipe | UpdateRecipe | DeleteRecipe | StoreRecipes | FetchRecipes;

+ 39
- 0
src/app/recipes/ngrx/recipe.effects.ts 파일 보기

@@ -0,0 +1,39 @@
import { Injectable } from "@angular/core";
import { Actions, Effect } from "@ngrx/effects";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/withLatestFrom';
import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http';
import { Store } from "@ngrx/store";

import * as RecipeActions from '../ngrx/recipe.actions';
import { Recipe } from "../recipe.model";
import * as fromRecipe from './recipe.reducers';

@Injectable()
export class RecipeEffects {
readonly baseUrl: string = 'https://my-recipe-book-cb837.firebaseio.com/';

constructor(private actions$: Actions,
private httpClient: HttpClient,
private store: Store<fromRecipe.FeatureState>) {}

@Effect()
recipeFetch = this.actions$.ofType(RecipeActions.FETCH_RECIPES)
.switchMap((action: RecipeActions.FetchRecipes) => {
return this.httpClient.get<Recipe[]>(this.baseUrl + 'recipes.json');
})
.map((recipes) => {
return {
type: RecipeActions.SET_RECIPES,
payload: recipes
};
});

@Effect({dispatch: false})
recipeStore = this.actions$.ofType(RecipeActions.STORE_RECIPES)
.withLatestFrom(this.store.select('recipes'))
.switchMap(([action, state]) => {
return this.httpClient.put(this.baseUrl + 'recipes.json', state.recipes);
});
}

+ 52
- 0
src/app/recipes/ngrx/recipe.reducers.ts 파일 보기

@@ -0,0 +1,52 @@
import { Recipe } from "../recipe.model";
import * as RecipeActions from "./recipe.actions";
import * as fromApp from "../../ngrx/app.reducers";

// RecipeState, only for a feature of the app, not the whole app
export interface FeatureState extends fromApp.AppState {
recipes: State
}

export interface State {
recipes: Recipe[];
}

const initialState: State = {
recipes: []
};

export function recipeReducer(state = initialState, action: RecipeActions.RecipeActions) {
switch(action.type) {
case RecipeActions.SET_RECIPES:
return {
...state,
recipes: [...action.payload]
};
case RecipeActions.ADD_RECIPE:
return {
...state,
recipes: [...state.recipes, action.payload]
};
case RecipeActions.UPDATE_RECIPE:
const recipe = state.recipes[action.payload.index];
const newRecipe = {
...recipe,
...action.payload.newRecipe
};
const recipes = [...state.recipes];
recipes[action.payload.index] = newRecipe;
return {
...state,
recipes: recipes
}
case RecipeActions.DELETE_RECIPE:
const oldRecipes = [...state.recipes];
oldRecipes.splice(action.payload, 1);
return {
...state,
recipes: oldRecipes
}
default:
return state;
}
}

+ 4
- 4
src/app/recipes/recipe-detail/recipe-detail.component.html 파일 보기

@@ -1,11 +1,11 @@
<div class="row">
<div class="col-xs-12">
<img [src]="recipe.imagePath" alt="" class="img-responsive" style="max-height: 300px;">
<img [src]="(recipeState | async).recipes[id].imagePath" alt="" class="img-responsive" style="max-height: 300px;">
</div>
</div>
<div class="row">
<div class="col-xs-12">
<h1>{{ recipe.name }}</h1>
<h1>{{ (recipeState | async).recipes[id].name }}</h1>
</div>
</div>
<div class="row">
@@ -26,7 +26,7 @@
</div>
<div class="row">
<div class="col-xs-12">
{{ recipe.description }}
{{ (recipeState | async).recipes[id].description }}
</div>
</div>
<div class="row">
@@ -34,7 +34,7 @@
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let ingredient of recipe.ingredients">
*ngFor="let ingredient of (recipeState | async).recipes[id].ingredients">
{{ ingredient.name }} - {{ ingredient.amount }}
</li>
</ul>


+ 18
- 8
src/app/recipes/recipe-detail/recipe-detail.component.ts 파일 보기

@@ -1,8 +1,13 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';

import { Recipe } from '../recipe.model';
import { RecipeService } from '../recipe.service';
import * as ShoppingListActions from '../../shopping-list/ngrx/shopping-list.actions';
import * as fromApp from '../../ngrx/app.reducers';
import * as fromRecipe from '../ngrx/recipe.reducers';
import * as RecipeActions from '../ngrx/recipe.actions';

@Component({
selector: 'app-recipe-detail',
@@ -10,24 +15,29 @@ import { RecipeService } from '../recipe.service';
styleUrls: ['./recipe-detail.component.css']
})
export class RecipeDetailComponent implements OnInit {
recipe: Recipe;
recipeState: Observable<fromRecipe.State>;
id: number;
constructor(private recipeService: RecipeService,
private route: ActivatedRoute,
private router: Router) { }
constructor(private route: ActivatedRoute,
private router: Router,
private store: Store<fromRecipe.FeatureState>) { }

ngOnInit() {
this.route.params.subscribe(
(params: Params) => {
this.id = +params['id'];
this.recipe = this.recipeService.getRecipe(this.id);
this.recipeState = this.store.select('recipes');
}
);
}

onAddToShoppingList() {
this.recipeService.addIngredientsToShoppingList(this.recipe.ingredients);
this.store.select('recipes')
.take(1)
.subscribe((recipeState: fromRecipe.State) => {
this.store.dispatch(new ShoppingListActions.AddIngredients(recipeState.recipes[this.id].ingredients));
});
}

onEditRecipe() {
@@ -35,7 +45,7 @@ export class RecipeDetailComponent implements OnInit {
}

onDelete() {
this.recipeService.delteRecipe(this.id);
this.store.dispatch(new RecipeActions.DeleteRecipe(this.id));
this.router.navigate(['/recipes']);
}
}

+ 24
- 23
src/app/recipes/recipe-edit/recipe-edit.component.ts 파일 보기

@@ -1,8 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { FormArray, FormGroup, FormControl, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';

import { RecipeService } from '../recipe.service';
import * as fromRecipe from '../ngrx/recipe.reducers';
import * as RecipeActions from '../ngrx/recipe.actions';

@Component({
selector: 'app-recipe-edit',
@@ -15,8 +17,8 @@ export class RecipeEditComponent implements OnInit {
recipeForm: FormGroup

constructor(private route: ActivatedRoute,
private recipeService: RecipeService,
private router: Router) { }
private router: Router,
private store: Store<fromRecipe.FeatureState>) { }

ngOnInit() {
this.route.params.subscribe(
@@ -29,14 +31,10 @@ export class RecipeEditComponent implements OnInit {
}

onSubmit() {
/* const newRecipe = new Recipe(this.recipeForm.value['name'],
this.recipeForm.valid['description'],
this.recipeForm.valid['imagePath'],
this.recipeForm.value['ingredients']); */
if (this.editMode) {
this.recipeService.updateRecpe(this.id, this.recipeForm.value);
this.store.dispatch(new RecipeActions.UpdateRecipe({index: this.id, newRecipe: this.recipeForm.value}));
} else {
this.recipeService.addRecipe(this.recipeForm.value);
this.store.dispatch(new RecipeActions.AddRecipe(this.recipeForm.value));
}
this.router.navigate(['../'], {relativeTo: this.route});
}
@@ -65,20 +63,23 @@ export class RecipeEditComponent implements OnInit {
let recipeIngredients = new FormArray([]);

if (this.editMode) {
const recipe = this.recipeService.getRecipe(this.id);
recipeName = recipe.name;
recipeImagePath = recipe.imagePath;
recipeDescription = recipe.description;
if (recipe['ingredients']) {
for (let ingredient of recipe.ingredients) {
recipeIngredients.push(
new FormGroup({
'name': new FormControl(ingredient.name, Validators.required),
'amount': new FormControl(ingredient.amount, [Validators.required, Validators.pattern(/^[1-9]+[0-9]*$/)])
})
);
}
}
this.store.select('recipes')
.take(1)
.subscribe((recipeState: fromRecipe.State) => {
const recipe = recipeState.recipes[this.id];recipeName = recipe.name;
recipeImagePath = recipe.imagePath;
recipeDescription = recipe.description;
if (recipe['ingredients']) {
for (let ingredient of recipe.ingredients) {
recipeIngredients.push(
new FormGroup({
'name': new FormControl(ingredient.name, Validators.required),
'amount': new FormControl(ingredient.amount, [Validators.required, Validators.pattern(/^[1-9]+[0-9]*$/)])
})
);
}
}
})
}

this.recipeForm = new FormGroup({


+ 1
- 1
src/app/recipes/recipe-list/recipe-list.component.html 파일 보기

@@ -7,7 +7,7 @@
<div class="row">
<div class="col-xs-12">
<app-recipe-item
*ngFor="let recipeElement of recipes; let i = index"
*ngFor="let recipeElement of (recipeState | async).recipes; let i = index"
[recipe]="recipeElement"
[index]="i"></app-recipe-item>
</div>


+ 10
- 21
src/app/recipes/recipe-list/recipe-list.component.ts 파일 보기

@@ -1,38 +1,27 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';

import { Recipe } from '../recipe.model';
import { RecipeService } from '../recipe.service';
import { Subscription } from 'rxjs/Subscription';

import * as fromRecipe from '../ngrx/recipe.reducers';

@Component({
selector: 'app-recipe-list',
templateUrl: './recipe-list.component.html',
styleUrls: ['./recipe-list.component.css']
})
export class RecipeListComponent implements OnInit, OnDestroy {
subscription: Subscription;
recipes: Recipe[];
export class RecipeListComponent implements OnInit {
recipeState: Observable<fromRecipe.State>;

constructor(private recipeService: RecipeService,
private router: Router,
private route: ActivatedRoute) { }
constructor(private router: Router,
private route: ActivatedRoute,
private store: Store<fromRecipe.FeatureState>) { }

ngOnInit() {
this.subscription = this.recipeService.recipesChanged.subscribe(
(recipes: Recipe[]) => {
this.recipes = recipes;
}
);
this.recipes = this.recipeService.getRecipes();
this.recipeState = this.store.select('recipes');
}

onNewRecipe() {
this.router.navigate(['new'], {relativeTo: this.route});
}

ngOnDestroy() {
this.subscription.unsubscribe();
}
}

+ 0
- 46
src/app/recipes/recipe.service.ts 파일 보기

@@ -1,46 +0,0 @@
import { Recipe } from "./recipe.model";
import { EventEmitter, Injectable } from "@angular/core";
import { Ingredient } from "../shared/ingredient.model";
import { ShoppingListService } from "../shopping-list/shopping-list.service";
import { Subject } from "rxjs/Subject";
import { nextTick } from "q";

@Injectable()
export class RecipeService {
recipesChanged = new Subject<Recipe[]>();
private recipes: Recipe[] = [];

constructor(private shoppingListService: ShoppingListService) { }

replaceRecipes(recipes: Recipe[]) {
this.recipes = recipes;
this.recipesChanged.next(this.recipes.slice());
}

getRecipes() {
return this.recipes.slice();
}

getRecipe(index: number) {
return this.recipes[index];
}

addIngredientsToShoppingList(ingredients: Ingredient[]) {
this.shoppingListService.addIngredients(ingredients);
}

addRecipe(recipe: Recipe) {
this.recipes.push(recipe);
this.recipesChanged.next(this.recipes.slice());
}

updateRecpe(index: number, newRecipe: Recipe) {
this.recipes[index] = newRecipe;
this.recipesChanged.next(this.recipes.slice());
}

delteRecipe(index: number) {
this.recipes.splice(index, 1);
this.recipesChanged.next(this.recipes.slice());
}
}

+ 0
- 2
src/app/recipes/recipes.component.ts 파일 보기

@@ -1,7 +1,5 @@
import { Component, OnInit } from '@angular/core';

import { RecipeService } from './recipe.service';

@Component({
selector: 'app-recipes',
templateUrl: './recipes.component.html',


+ 7
- 2
src/app/recipes/recipes.module.ts 파일 보기

@@ -1,6 +1,8 @@
import { NgModule } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";
import { StoreModule } from "@ngrx/store";
import { EffectsModule } from "@ngrx/effects";

import { RecipesComponent } from "./recipes.component";
import { RecipeStartComponent } from "./recipe-start/recipe-start.component";
@@ -10,7 +12,8 @@ import { RecipeDetailComponent } from "./recipe-detail/recipe-detail.component";
import { RecipeItemComponent } from "./recipe-list/recipe-item/recipe-item.component";
import { RecipesRoutingModule } from "./recipes-routing.module";
import { SharedModule } from "../shared/shared.module";

import { recipeReducer } from "./ngrx/recipe.reducers";
import { RecipeEffects } from "./ngrx/recipe.effects";

@NgModule({
declarations: [
@@ -25,7 +28,9 @@ import { SharedModule } from "../shared/shared.module";
CommonModule,
ReactiveFormsModule,
RecipesRoutingModule,
SharedModule
SharedModule,
StoreModule.forFeature('recipes', recipeReducer),
EffectsModule.forFeature([RecipeEffects])
]
})
export class RecipesModule {


+ 13
- 6
src/app/shared/auth.interceptor.ts 파일 보기

@@ -1,16 +1,23 @@
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { Store } from "@ngrx/store";
import "rxjs/add/operator/switchMap";
import "rxjs/add/operator/take";

import * as fromApp from '../ngrx/app.reducers';
import * as fromAuth from '../auth/ngrx/auth.reducers';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
constructor(private store: Store<fromApp.AppState>) {}

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const copiedReq = req.clone({
params: req.params.set('auth', this.authService.getToken())
});
return next.handle(copiedReq);
return this.store.select('auth')
.take(1)
.switchMap((authState: fromAuth.State) => {
const copiedReq = req.clone({params: req.params.set('auth', authState.token)});
return next.handle(copiedReq);
});
}
}

+ 0
- 29
src/app/shared/data-storage.service.ts 파일 보기

@@ -1,29 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'

import { RecipeService } from '../recipes/recipe.service';
import { Recipe } from '../recipes/recipe.model';

@Injectable()
export class DataStorageService {
readonly baseUrl: string = 'https://my-recipe-book-cb837.firebaseio.com/';

constructor(private httpClient: HttpClient,
private recipeService: RecipeService) {}

storeRecipes() {
// const token = this.authService.getToken();
// return this.httpClient.put(this.baseUrl + 'recipes.json?auth=' + token, this.recipeService.getRecipes());
// const req = new HttpRequest('PUT', this.baseUrl, this.recipeService.getRecipes(), {reportProgress: true, params: new HttpParams().set('auth', token)});
// return this.httpClient.request(req);
return this.httpClient.put(this.baseUrl + 'recipes.json', this.recipeService.getRecipes());
}

fetchRecipes() {
this.httpClient.get<Recipe[]>(this.baseUrl + 'recipes.json').subscribe(
recipes => {
this.recipeService.replaceRecipes(recipes);
}
)
}
}

+ 2
- 2
src/app/shared/logging.interceptor.ts 파일 보기

@@ -4,10 +4,10 @@ import 'rxjs/add/operator/do';

export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// do without consuming it!
// do() without consuming it!
return next.handle(req).do(
event => {
console.log('Logging interceptor', event);
// console.log('Logging interceptor', event);
}
)
}

+ 46
- 0
src/app/shopping-list/ngrx/shopping-list.actions.ts 파일 보기

@@ -0,0 +1,46 @@
import { Action } from '@ngrx/store';

import { Ingredient } from '../../shared/ingredient.model';

export const ADD_INGREDIENT = 'ADD_INGREDIENT';
export const ADD_INGREDIENTS = 'ADD_INGREDIENTS';
export const UPDATE_INGREDIENT = 'UPDATE_INGREDIENT';
export const DELETE_INGREDIENT = 'DELETE_INGREDIENT';
export const START_EDIT = 'START_EDIT';
export const STOP_EDIT = 'STOP_EDIT';

export class AddIngredient implements Action {
readonly type = ADD_INGREDIENT;
constructor(public payload: Ingredient) {}
}

export class AddIngredients implements Action {
readonly type = ADD_INGREDIENTS;
constructor(public payload: Ingredient[]) {}
}

export class UpdateIngredient implements Action {
readonly type = UPDATE_INGREDIENT;
constructor(public payload: Ingredient) {}
}

export class DeleteIngredient implements Action {
readonly type = DELETE_INGREDIENT;
}

export class StartEdit implements Action {
readonly type = START_EDIT;
constructor(public payload: number) {}
}

export class StopEdit implements Action {
readonly type = STOP_EDIT;
}



export type ShoppingListActions = AddIngredient | AddIngredients | UpdateIngredient | DeleteIngredient | StartEdit | StopEdit;

+ 70
- 0
src/app/shopping-list/ngrx/shopping-list.reducers.ts 파일 보기

@@ -0,0 +1,70 @@
import * as ShoppingListActions from './shopping-list.actions';

import { Ingredient } from '../../shared/ingredient.model';

export interface State {
ingredients: Ingredient[];
editedIngredient: Ingredient;
editedIngredientIndex: number;
}

const initialState: State = {
ingredients: [
new Ingredient('Banana', 10)
],
editedIngredient: null,
editedIngredientIndex: -1
};

export function shoppingListReducer(state = initialState, action: ShoppingListActions.ShoppingListActions) {
switch (action.type) {
case ShoppingListActions.ADD_INGREDIENT:
return {
...state,
ingredients: [...state.ingredients, action.payload]
};
case ShoppingListActions.ADD_INGREDIENTS:
return {
...state,
ingredients: [...state.ingredients, ...action.payload]
};
case ShoppingListActions.UPDATE_INGREDIENT:
const ingredient = state.ingredients[state.editedIngredientIndex];
const updatedIngredient = {
...ingredient,
...action.payload
}
const ingredients = [...state.ingredients];
ingredients[state.editedIngredientIndex] = updatedIngredient;
return {
...state,
ingredients: ingredients,
editedIngredient: null,
editedIngredientIndex: -1
};
case ShoppingListActions.DELETE_INGREDIENT:
const oldIngredients = [...state.ingredients];
oldIngredients.splice(state.editedIngredientIndex, 1);
return {
...state,
ingredients: oldIngredients,
editedIngredient: null,
editedIngredientIndex: -1
};
case ShoppingListActions.START_EDIT:
const editedIngredient = {...state.ingredients[action.payload]};
return {
...state,
editedIngredient: editedIngredient,
editedIngredientIndex: action.payload
}
case ShoppingListActions.STOP_EDIT:
return {
...state,
editedIngredient: null,
editedIngredientIndex: -1
}
default:
return state;
}
}

+ 20
- 15
src/app/shopping-list/shopping-edit/shopping-edit.component.ts 파일 보기

@@ -1,9 +1,11 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Subscription } from 'rxjs/Subscription';
import { Store } from '@ngrx/store';

import { Ingredient } from '../../shared/ingredient.model';
import { ShoppingListService } from '../shopping-list.service';
import * as ShoppingListActions from '../ngrx/shopping-list.actions';
import * as fromApp from '../../ngrx/app.reducers'

@Component({
selector: 'app-shopping-edit',
@@ -14,21 +16,23 @@ export class ShoppingEditComponent implements OnInit, OnDestroy {
@ViewChild('f') shoppingListForm: NgForm;
subscription: Subscription
editMode = false;
editedItemIndex: number;
editedItem: Ingredient;

constructor(private shoppingListService: ShoppingListService) { }
constructor(private store: Store<fromApp.AppState>) { }

ngOnInit() {
this.subscription = this.shoppingListService.startedEditing.subscribe(
(index: number) => {
this.editedItemIndex = index;
this.editMode = true;
this.editedItem = this.shoppingListService.getIngredient(index);
this.shoppingListForm.setValue({
name: this.editedItem.name,
amount: this.editedItem.amount
})
this.subscription = this.store.select('shoppingList').subscribe(
data => {
if (data.editedIngredientIndex > -1) {
this.editedItem = data.editedIngredient;
this.editMode = true;
this.shoppingListForm.setValue({
name: this.editedItem.name,
amount: this.editedItem.amount
})
} else {
this.editMode = false;
}
}
);
}
@@ -37,9 +41,9 @@ export class ShoppingEditComponent implements OnInit, OnDestroy {
const value = form.value
const newIngredient = new Ingredient(value.name, value.amount);
if (this.editMode) {
this.shoppingListService.updateIngredient(this.editedItemIndex, newIngredient);
this.store.dispatch(new ShoppingListActions.UpdateIngredient(newIngredient));
} else {
this.shoppingListService.addIngredient(newIngredient);
this.store.dispatch(new ShoppingListActions.AddIngredient(newIngredient));
}
this.editMode = false;
form.reset();
@@ -51,11 +55,12 @@ export class ShoppingEditComponent implements OnInit, OnDestroy {
}

onDelete() {
this.shoppingListService.deleteIngredient(this.editedItemIndex);
this.store.dispatch(new ShoppingListActions.DeleteIngredient());
this.onClear();
}

ngOnDestroy() {
this.store.dispatch(new ShoppingListActions.StopEdit());
this.subscription.unsubscribe();
}
}

+ 1
- 1
src/app/shopping-list/shopping-list.component.html 파일 보기

@@ -6,7 +6,7 @@
<a
class="list-group-item"
style="cursor: pointer;"
*ngFor="let ingredient of ingredients; let i = index"
*ngFor="let ingredient of (shoppingListState | async).ingredients; let i = index"
(click)="onEditItem(i)"
>
{{ ingredient.name }} ({{ ingredient.amount }})


+ 10
- 18
src/app/shopping-list/shopping-list.component.ts 파일 보기

@@ -1,34 +1,26 @@
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';

import { Ingredient } from '../shared/ingredient.model';
import { ShoppingListService } from './shopping-list.service';
import { Subscription } from 'rxjs/Subscription';
import { OnDestroy } from '@angular/core/src/metadata/lifecycle_hooks';
import * as ShoppingListActions from './ngrx/shopping-list.actions';
import * as fromApp from '../ngrx/app.reducers'

@Component({
selector: 'app-shopping-list',
templateUrl: './shopping-list.component.html',
styleUrls: ['./shopping-list.component.css']
})
export class ShoppingListComponent implements OnInit, OnDestroy {
ingredients: Ingredient[];
private subscription: Subscription
export class ShoppingListComponent implements OnInit {
shoppingListState: Observable<{ingredients: Ingredient[]}>;

constructor(private shoppingListService: ShoppingListService) { }
constructor(private store: Store<fromApp.AppState>) { }

ngOnInit() {
this.ingredients = this.shoppingListService.getIngredients();
this.subscription = this.shoppingListService.ingredientsChanged.subscribe(
(ingredients: Ingredient[]) => {
this.ingredients = ingredients;
}
);
this.shoppingListState = this.store.select('shoppingList');
}

onEditItem(index: number) {
this.shoppingListService.startedEditing.next(index);
}

ngOnDestroy() {
this.subscription.unsubscribe();
this.store.dispatch(new ShoppingListActions.StartEdit(index));
}
}

+ 0
- 36
src/app/shopping-list/shopping-list.service.ts 파일 보기

@@ -1,36 +0,0 @@
import { Ingredient } from "../shared/ingredient.model";
import { Subject } from "rxjs/Subject";

export class ShoppingListService {
ingredientsChanged = new Subject<Ingredient[]>();
startedEditing = new Subject<number>();
private ingredients: Ingredient[] = [];

getIngredients() {
return this.ingredients.slice();
}
getIngredient(index: number) {
return this.ingredients[index];
}

addIngredient(ingredient: Ingredient) {
this.ingredients.push(ingredient);
this.ingredientsChanged.next(this.ingredients.slice());
}

addIngredients(ingredients: Ingredient[]) {
this.ingredients.push(...ingredients);
this.ingredientsChanged.next(this.ingredients.slice());
}

updateIngredient(index: number, newIngredient: Ingredient) {
this.ingredients[index] = newIngredient;
this.ingredientsChanged.next(this.ingredients.slice());
}

deleteIngredient(index: number) {
this.ingredients.splice(index, 1);
this.ingredientsChanged.next(this.ingredients.slice());
}
}

불러오는 중...
취소
저장