From 7e16021a74b3f2f726ba554b614ebf6300d5463a Mon Sep 17 00:00:00 2001 From: Nils Dittberner Date: Wed, 29 Nov 2017 09:48:12 +0100 Subject: [PATCH 01/11] 24-307 Loading states with NgRx --- package-lock.json | 5 +++++ package.json | 1 + src/app/app.module.ts | 5 ++++- .../shopping-list/ngrx/shopping-list.actions.ts | 12 +++++++++++ .../shopping-list/ngrx/shopping-list.reducers.ts | 23 ++++++++++++++++++++ src/app/shopping-list/shopping-list.component.html | 2 +- src/app/shopping-list/shopping-list.component.ts | 25 ++++++++++++---------- 7 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 src/app/shopping-list/ngrx/shopping-list.actions.ts create mode 100644 src/app/shopping-list/ngrx/shopping-list.reducers.ts diff --git a/package-lock.json b/package-lock.json index d28f638..c0a10dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -336,6 +336,11 @@ "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.4.tgz", "integrity": "sha512-jEpglcwMlwdXc/JgvJaJtCSkPMktnFeI0gAZxPrmbJxKVzMZJ2zM582NbW/r6M22pSdNWjcWeg1I2LRg3jQGQA==" }, + "@ngrx/store": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-4.1.1.tgz", + "integrity": "sha1-aA403yd16IUnVO13f/rJW9gbfeA=" + }, "@ngtools/json-schema": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz", diff --git a/package.json b/package.json index 5cea932..98c8c36 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@angular/platform-browser": "^5.0.0", "@angular/platform-browser-dynamic": "^5.0.0", "@angular/router": "^5.0.0", + "@ngrx/store": "^4.1.1", "bootstrap": "^3.3.7", "core-js": "^2.4.1", "firebase": "^4.6.2", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9e779e7..55d8b72 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; +import { StoreModule } from '@ngrx/store'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; @@ -8,6 +9,7 @@ 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 { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers'; @NgModule({ declarations: [ @@ -20,7 +22,8 @@ import { CoreModule } from './core/core.module'; SharedModule, ShoppingListModule, AuthModule, - CoreModule + CoreModule, + StoreModule.forRoot({shoppingList: shoppingListReducer}) ], bootstrap: [AppComponent] }) diff --git a/src/app/shopping-list/ngrx/shopping-list.actions.ts b/src/app/shopping-list/ngrx/shopping-list.actions.ts new file mode 100644 index 0000000..2bf81d7 --- /dev/null +++ b/src/app/shopping-list/ngrx/shopping-list.actions.ts @@ -0,0 +1,12 @@ +import { Action } from '@ngrx/store'; + +import { Ingredient } from '../../shared/ingredient.model'; + +export const ADD_INGREDIENT = 'ADD_INGREDIENT'; + +export class AddIngredient implements Action { + readonly type = ADD_INGREDIENT; + payload: Ingredient; +} + +export type ShoppingListActions = AddIngredient; \ No newline at end of file diff --git a/src/app/shopping-list/ngrx/shopping-list.reducers.ts b/src/app/shopping-list/ngrx/shopping-list.reducers.ts new file mode 100644 index 0000000..ee47e22 --- /dev/null +++ b/src/app/shopping-list/ngrx/shopping-list.reducers.ts @@ -0,0 +1,23 @@ +import * as ShoppingListActions from './shopping-list.actions'; + +import { Ingredient } from '../../shared/ingredient.model'; + +export const ADD_INGREDIENT = 'ADD_INGREDIENT'; + +const initialState = { + ingredients: [ + new Ingredient('Banana', 10) + ] +}; + +export function shoppingListReducer(state = initialState, action: ShoppingListActions.ShoppingListActions) { + switch (action.type) { + case ShoppingListActions.ADD_INGREDIENT: + return { + ...state, + ingredients: [...state.ingredients, action.payload] + }; + default: + return state; + } +} \ No newline at end of file diff --git a/src/app/shopping-list/shopping-list.component.html b/src/app/shopping-list/shopping-list.component.html index ed3016e..2c2ddaf 100644 --- a/src/app/shopping-list/shopping-list.component.html +++ b/src/app/shopping-list/shopping-list.component.html @@ -6,7 +6,7 @@ {{ ingredient.name }} ({{ ingredient.amount }}) diff --git a/src/app/shopping-list/shopping-list.component.ts b/src/app/shopping-list/shopping-list.component.ts index e6d6052..8842acf 100644 --- a/src/app/shopping-list/shopping-list.component.ts +++ b/src/app/shopping-list/shopping-list.component.ts @@ -1,8 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; +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'; @Component({ selector: 'app-shopping-list', @@ -10,18 +12,19 @@ import { OnDestroy } from '@angular/core/src/metadata/lifecycle_hooks'; styleUrls: ['./shopping-list.component.css'] }) export class ShoppingListComponent implements OnInit, OnDestroy { - ingredients: Ingredient[]; + shoppingListState: Observable<{ingredients: Ingredient[]}>; private subscription: Subscription - constructor(private shoppingListService: ShoppingListService) { } + constructor(private shoppingListService: ShoppingListService, + private store: Store<{shoppingList: {ingredients: Ingredient[]}}>) { } ngOnInit() { - this.ingredients = this.shoppingListService.getIngredients(); - this.subscription = this.shoppingListService.ingredientsChanged.subscribe( - (ingredients: Ingredient[]) => { - this.ingredients = ingredients; - } - ); + this.shoppingListState = this.store.select('shoppingList'); + // this.subscription = this.shoppingListService.ingredientsChanged.subscribe( + // (ingredients: Ingredient[]) => { + // this.ingredients = ingredients; + // } + // ); } onEditItem(index: number) { From 7aa097ca070403bc4026c95654fb7dbe43a128fa Mon Sep 17 00:00:00 2001 From: Nils Dittberner Date: Wed, 29 Nov 2017 09:57:25 +0100 Subject: [PATCH 02/11] 24-307 Setting state with actions --- src/app/shopping-list/ngrx/shopping-list.actions.ts | 3 ++- .../shopping-edit/shopping-edit.component.ts | 7 +++++-- src/app/shopping-list/shopping-list.component.ts | 15 ++------------- src/app/shopping-list/shopping-list.service.ts | 9 --------- 4 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/app/shopping-list/ngrx/shopping-list.actions.ts b/src/app/shopping-list/ngrx/shopping-list.actions.ts index 2bf81d7..d755ecc 100644 --- a/src/app/shopping-list/ngrx/shopping-list.actions.ts +++ b/src/app/shopping-list/ngrx/shopping-list.actions.ts @@ -6,7 +6,8 @@ export const ADD_INGREDIENT = 'ADD_INGREDIENT'; export class AddIngredient implements Action { readonly type = ADD_INGREDIENT; - payload: Ingredient; + + constructor(public payload: Ingredient) {} } export type ShoppingListActions = AddIngredient; \ No newline at end of file diff --git a/src/app/shopping-list/shopping-edit/shopping-edit.component.ts b/src/app/shopping-list/shopping-edit/shopping-edit.component.ts index 645bb70..240161b 100644 --- a/src/app/shopping-list/shopping-edit/shopping-edit.component.ts +++ b/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'; @Component({ selector: 'app-shopping-edit', @@ -17,7 +19,8 @@ export class ShoppingEditComponent implements OnInit, OnDestroy { editedItemIndex: number; editedItem: Ingredient; - constructor(private shoppingListService: ShoppingListService) { } + constructor(private shoppingListService: ShoppingListService, + private store: Store<{shoppingList: {ingredients: Ingredient[]}}>) { } ngOnInit() { this.subscription = this.shoppingListService.startedEditing.subscribe( @@ -39,7 +42,7 @@ export class ShoppingEditComponent implements OnInit, OnDestroy { if (this.editMode) { this.shoppingListService.updateIngredient(this.editedItemIndex, newIngredient); } else { - this.shoppingListService.addIngredient(newIngredient); + this.store.dispatch(new ShoppingListActions.AddIngredient(newIngredient)); } this.editMode = false; form.reset(); diff --git a/src/app/shopping-list/shopping-list.component.ts b/src/app/shopping-list/shopping-list.component.ts index 8842acf..772cbcd 100644 --- a/src/app/shopping-list/shopping-list.component.ts +++ b/src/app/shopping-list/shopping-list.component.ts @@ -1,5 +1,4 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { Subscription } from 'rxjs/Subscription'; +import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; @@ -11,27 +10,17 @@ import { ShoppingListService } from './shopping-list.service'; templateUrl: './shopping-list.component.html', styleUrls: ['./shopping-list.component.css'] }) -export class ShoppingListComponent implements OnInit, OnDestroy { +export class ShoppingListComponent implements OnInit { shoppingListState: Observable<{ingredients: Ingredient[]}>; - private subscription: Subscription constructor(private shoppingListService: ShoppingListService, private store: Store<{shoppingList: {ingredients: Ingredient[]}}>) { } ngOnInit() { this.shoppingListState = this.store.select('shoppingList'); - // this.subscription = this.shoppingListService.ingredientsChanged.subscribe( - // (ingredients: Ingredient[]) => { - // this.ingredients = ingredients; - // } - // ); } onEditItem(index: number) { this.shoppingListService.startedEditing.next(index); } - - ngOnDestroy() { - this.subscription.unsubscribe(); - } } diff --git a/src/app/shopping-list/shopping-list.service.ts b/src/app/shopping-list/shopping-list.service.ts index 2f3ba93..626a4dc 100644 --- a/src/app/shopping-list/shopping-list.service.ts +++ b/src/app/shopping-list/shopping-list.service.ts @@ -5,20 +5,11 @@ export class ShoppingListService { ingredientsChanged = new Subject(); startedEditing = new Subject(); 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()); From 569a9da5033658ecc18ba6d9c83ba1a74c999c2e Mon Sep 17 00:00:00 2001 From: Nils Dittberner Date: Mon, 4 Dec 2017 07:34:04 +0100 Subject: [PATCH 03/11] 24-314 Moving data logic to NgRx, removing shopping list service --- src/app/core/core.module.ts | 2 - .../recipe-detail/recipe-detail.component.ts | 8 ++- src/app/recipes/recipe.service.ts | 14 ++--- .../shopping-list/ngrx/shopping-list.actions.ts | 35 ++++++++++++- .../shopping-list/ngrx/shopping-list.reducers.ts | 60 +++++++++++++++++++++- .../shopping-edit/shopping-edit.component.ts | 32 ++++++------ src/app/shopping-list/shopping-list.component.ts | 8 +-- src/app/shopping-list/shopping-list.service.ts | 27 ---------- 8 files changed, 122 insertions(+), 64 deletions(-) delete mode 100644 src/app/shopping-list/shopping-list.service.ts diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 285be48..08b7925 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -5,7 +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"; @@ -26,7 +25,6 @@ import { LoggingInterceptor } from "../shared/logging.interceptor"; HeaderComponent ], providers: [ - ShoppingListService, RecipeService, DataStorageService, AuthService, diff --git a/src/app/recipes/recipe-detail/recipe-detail.component.ts b/src/app/recipes/recipe-detail/recipe-detail.component.ts index 2239be4..96c211b 100644 --- a/src/app/recipes/recipe-detail/recipe-detail.component.ts +++ b/src/app/recipes/recipe-detail/recipe-detail.component.ts @@ -1,8 +1,11 @@ import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; +import { Store } from '@ngrx/store'; import { Recipe } from '../recipe.model'; import { RecipeService } from '../recipe.service'; +import * as ShoppingListActions from '../../shopping-list/ngrx/shopping-list.actions'; +import * as fromShoppingList from '../../shopping-list/ngrx/shopping-list.reducers'; @Component({ selector: 'app-recipe-detail', @@ -15,7 +18,8 @@ export class RecipeDetailComponent implements OnInit { constructor(private recipeService: RecipeService, private route: ActivatedRoute, - private router: Router) { } + private router: Router, + private store: Store) { } ngOnInit() { this.route.params.subscribe( @@ -27,7 +31,7 @@ export class RecipeDetailComponent implements OnInit { } onAddToShoppingList() { - this.recipeService.addIngredientsToShoppingList(this.recipe.ingredients); + this.store.dispatch(new ShoppingListActions.AddIngredients(this.recipe.ingredients)); } onEditRecipe() { diff --git a/src/app/recipes/recipe.service.ts b/src/app/recipes/recipe.service.ts index d2a7c47..cecd1ae 100644 --- a/src/app/recipes/recipe.service.ts +++ b/src/app/recipes/recipe.service.ts @@ -1,17 +1,13 @@ +import { EventEmitter } from "@angular/core"; +import { Subject } from "rxjs/Subject"; + 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(); private recipes: Recipe[] = []; - constructor(private shoppingListService: ShoppingListService) { } - replaceRecipes(recipes: Recipe[]) { this.recipes = recipes; this.recipesChanged.next(this.recipes.slice()); @@ -25,10 +21,6 @@ export class RecipeService { return this.recipes[index]; } - addIngredientsToShoppingList(ingredients: Ingredient[]) { - this.shoppingListService.addIngredients(ingredients); - } - addRecipe(recipe: Recipe) { this.recipes.push(recipe); this.recipesChanged.next(this.recipes.slice()); diff --git a/src/app/shopping-list/ngrx/shopping-list.actions.ts b/src/app/shopping-list/ngrx/shopping-list.actions.ts index d755ecc..6ea1850 100644 --- a/src/app/shopping-list/ngrx/shopping-list.actions.ts +++ b/src/app/shopping-list/ngrx/shopping-list.actions.ts @@ -3,6 +3,11 @@ 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; @@ -10,4 +15,32 @@ export class AddIngredient implements Action { constructor(public payload: Ingredient) {} } -export type ShoppingListActions = AddIngredient; \ No newline at end of file +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; \ No newline at end of file diff --git a/src/app/shopping-list/ngrx/shopping-list.reducers.ts b/src/app/shopping-list/ngrx/shopping-list.reducers.ts index ee47e22..d9d234d 100644 --- a/src/app/shopping-list/ngrx/shopping-list.reducers.ts +++ b/src/app/shopping-list/ngrx/shopping-list.reducers.ts @@ -2,12 +2,27 @@ import * as ShoppingListActions from './shopping-list.actions'; import { Ingredient } from '../../shared/ingredient.model'; +export interface AppState { + shoppingList: State +} + +export interface State { + ingredients: Ingredient[]; + editedIngredient: Ingredient; + editedIngredientIndex: number; +} + export const ADD_INGREDIENT = 'ADD_INGREDIENT'; +export const ADD_INGREDIENTS = 'ADD_INGREDIENTS'; +export const UPDATE_INGREDIENT = 'UPDATE_INGREDIENT'; +export const DELETE_INGREDIENT = 'DELETE_INGREDIENT'; -const initialState = { +const initialState: State = { ingredients: [ new Ingredient('Banana', 10) - ] + ], + editedIngredient: null, + editedIngredientIndex: -1 }; export function shoppingListReducer(state = initialState, action: ShoppingListActions.ShoppingListActions) { @@ -17,6 +32,47 @@ export function shoppingListReducer(state = initialState, action: ShoppingListAc ...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; } diff --git a/src/app/shopping-list/shopping-edit/shopping-edit.component.ts b/src/app/shopping-list/shopping-edit/shopping-edit.component.ts index 240161b..198040e 100644 --- a/src/app/shopping-list/shopping-edit/shopping-edit.component.ts +++ b/src/app/shopping-list/shopping-edit/shopping-edit.component.ts @@ -4,8 +4,8 @@ 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 fromShoppingList from '../ngrx/shopping-list.reducers'; @Component({ selector: 'app-shopping-edit', @@ -16,22 +16,23 @@ export class ShoppingEditComponent implements OnInit, OnDestroy { @ViewChild('f') shoppingListForm: NgForm; subscription: Subscription editMode = false; - editedItemIndex: number; editedItem: Ingredient; - constructor(private shoppingListService: ShoppingListService, - private store: Store<{shoppingList: {ingredients: Ingredient[]}}>) { } + constructor(private store: Store) { } 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; + } } ); } @@ -40,7 +41,7 @@ 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.store.dispatch(new ShoppingListActions.AddIngredient(newIngredient)); } @@ -54,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(); } } diff --git a/src/app/shopping-list/shopping-list.component.ts b/src/app/shopping-list/shopping-list.component.ts index 772cbcd..9c865d8 100644 --- a/src/app/shopping-list/shopping-list.component.ts +++ b/src/app/shopping-list/shopping-list.component.ts @@ -3,7 +3,8 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import { Ingredient } from '../shared/ingredient.model'; -import { ShoppingListService } from './shopping-list.service'; +import * as ShoppingListActions from './ngrx/shopping-list.actions'; +import * as fromShoppingList from './ngrx/shopping-list.reducers'; @Component({ selector: 'app-shopping-list', @@ -13,14 +14,13 @@ import { ShoppingListService } from './shopping-list.service'; export class ShoppingListComponent implements OnInit { shoppingListState: Observable<{ingredients: Ingredient[]}>; - constructor(private shoppingListService: ShoppingListService, - private store: Store<{shoppingList: {ingredients: Ingredient[]}}>) { } + constructor(private store: Store) { } ngOnInit() { this.shoppingListState = this.store.select('shoppingList'); } onEditItem(index: number) { - this.shoppingListService.startedEditing.next(index); + this.store.dispatch(new ShoppingListActions.StartEdit(index)); } } diff --git a/src/app/shopping-list/shopping-list.service.ts b/src/app/shopping-list/shopping-list.service.ts deleted file mode 100644 index 626a4dc..0000000 --- a/src/app/shopping-list/shopping-list.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Ingredient } from "../shared/ingredient.model"; -import { Subject } from "rxjs/Subject"; - -export class ShoppingListService { - ingredientsChanged = new Subject(); - startedEditing = new Subject(); - private ingredients: Ingredient[] = []; - - getIngredient(index: number) { - return this.ingredients[index]; - } - - 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()); - } -} \ No newline at end of file From bae3653b7a280d8eab04a1f719006ea77f59a308 Mon Sep 17 00:00:00 2001 From: Nils Dittberner Date: Mon, 4 Dec 2017 09:33:57 +0100 Subject: [PATCH 04/11] 24-322 Switching to NgRx for authentication --- src/app/app.module.ts | 4 +-- src/app/auth/auth-guard.service.ts | 12 +++++-- src/app/auth/auth.service.ts | 42 ++++++++++++---------- src/app/auth/ngrx/auth.actions.ts | 26 ++++++++++++++ src/app/auth/ngrx/auth.reducers.ts | 35 ++++++++++++++++++ src/app/core/header/header.component.html | 6 ++-- src/app/core/header/header.component.ts | 22 ++++++++---- src/app/ngrx/app.reducers.ts | 14 ++++++++ .../recipe-detail/recipe-detail.component.ts | 4 +-- src/app/shared/auth.interceptor.ts | 19 ++++++---- .../shopping-list/ngrx/shopping-list.reducers.ts | 9 ----- .../shopping-edit/shopping-edit.component.ts | 4 +-- src/app/shopping-list/shopping-list.component.ts | 4 +-- 13 files changed, 147 insertions(+), 54 deletions(-) create mode 100644 src/app/auth/ngrx/auth.actions.ts create mode 100644 src/app/auth/ngrx/auth.reducers.ts create mode 100644 src/app/ngrx/app.reducers.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 55d8b72..cd2ab10 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,7 +9,7 @@ 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 { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers'; +import { appReducers } from './ngrx/app.reducers'; @NgModule({ declarations: [ @@ -23,7 +23,7 @@ import { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers ShoppingListModule, AuthModule, CoreModule, - StoreModule.forRoot({shoppingList: shoppingListReducer}) + StoreModule.forRoot(appReducers) ], bootstrap: [AppComponent] }) diff --git a/src/app/auth/auth-guard.service.ts b/src/app/auth/auth-guard.service.ts index 82a26ed..29a9958 100644 --- a/src/app/auth/auth-guard.service.ts +++ b/src/app/auth/auth-guard.service.ts @@ -1,14 +1,20 @@ 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) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { - return this.authService.isAuthenticated(); + return this.store.select('auth').map((authState: fromAuth.State) => { + return authState.authenticated; + }); } } \ No newline at end of file diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts index f264911..c5df36b 100644 --- a/src/app/auth/auth.service.ts +++ b/src/app/auth/auth.service.ts @@ -1,15 +1,30 @@ import * as firebase from 'firebase'; import { Router } from '@angular/router'; import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import * as fromApp from '../ngrx/app.reducers'; +import * as AuthActions from './ngrx/auth.actions'; @Injectable() export class AuthService { - token: string; - - constructor(private router: Router) {} + constructor(private router: Router, private store: Store) {} signupUser(email: string, password: string) { - firebase.auth().createUserWithEmailAndPassword(email, password).catch( + firebase.auth().createUserWithEmailAndPassword(email, password) + .then( + user => { + this.store.dispatch(new AuthActions.Signup()); + firebase.auth().currentUser.getIdToken() + .then( + (token: string) => { + this.store.dispatch(new AuthActions.SetToken(token)); + } + ); + + } + ) + .catch( error => console.log(error) ); } @@ -18,10 +33,13 @@ export class AuthService { firebase.auth().signInWithEmailAndPassword(email, password) .then( response => { + this.store.dispatch(new AuthActions.Signin()); this.router.navigate(['/']); firebase.auth().currentUser.getIdToken() .then( - (token: string) => this.token = token + (token: string) => { + this.store.dispatch(new AuthActions.SetToken(token)); + } ); } ) @@ -32,19 +50,7 @@ export class AuthService { logout() { firebase.auth().signOut(); - this.token = null; + this.store.dispatch(new AuthActions.Logout()); this.router.navigate(['/']); } - - getToken() { - firebase.auth().currentUser.getIdToken() - .then( - (token: string) => this.token = token - ); - return this.token; - } - - isAuthenticated() { - return this.token != null; - } } \ No newline at end of file diff --git a/src/app/auth/ngrx/auth.actions.ts b/src/app/auth/ngrx/auth.actions.ts new file mode 100644 index 0000000..019a88b --- /dev/null +++ b/src/app/auth/ngrx/auth.actions.ts @@ -0,0 +1,26 @@ +import { Action } from '@ngrx/store'; + +export const SIGNUP = 'SIGNUP'; +export const SIGNIN = 'SIGNIN'; +export const LOGOUT = 'LOGOUT'; +export const SET_TOKEN = 'SET_TOKEN'; + +export class Signup implements Action { + readonly type = SIGNUP; +} + +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 = Signup | Signin | Logout | SetToken; \ No newline at end of file diff --git a/src/app/auth/ngrx/auth.reducers.ts b/src/app/auth/ngrx/auth.reducers.ts new file mode 100644 index 0000000..67ad145 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/src/app/core/header/header.component.html b/src/app/core/header/header.component.html index a83d6b9..cd84455 100644 --- a/src/app/core/header/header.component.html +++ b/src/app/core/header/header.component.html @@ -10,12 +10,12 @@
  • Shopping List