瀏覽代碼

24-341 Fetching data with ngrx

ngrx
Nils Dittberner 8 年之前
父節點
當前提交
1bdf8d4131
共有 12 個文件被更改,包括 188 次插入79 次删除
  1. +2
    -1
      src/app/core/header/header.component.ts
  2. +43
    -0
      src/app/recipes/ngrx/recipe.actions.ts
  3. +28
    -0
      src/app/recipes/ngrx/recipe.effects.ts
  4. +52
    -0
      src/app/recipes/ngrx/recipe.reducers.ts
  5. +5
    -5
      src/app/recipes/recipe-detail/recipe-detail.component.html
  6. +14
    -8
      src/app/recipes/recipe-detail/recipe-detail.component.ts
  7. +24
    -23
      src/app/recipes/recipe-edit/recipe-edit.component.ts
  8. +1
    -1
      src/app/recipes/recipe-list/recipe-list.component.html
  9. +11
    -19
      src/app/recipes/recipe-list/recipe-list.component.ts
  10. +0
    -19
      src/app/recipes/recipe.service.ts
  11. +7
    -2
      src/app/recipes/recipes.module.ts
  12. +1
    -1
      src/app/shared/data-storage.service.ts

+ 2
- 1
src/app/core/header/header.component.ts 查看文件

@@ -6,6 +6,7 @@ import { DataStorageService } from "../../shared/data-storage.service";
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',
@@ -29,7 +30,7 @@ export class HeaderComponent implements OnInit {
}

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

onLogout() {


+ 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;

+ 28
- 0
src/app/recipes/ngrx/recipe.effects.ts 查看文件

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

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

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

@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
};
});

constructor(private actions$: Actions,
private httpClient: HttpClient) {}
}

+ 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: [new Recipe("Foo", "Bar", "image.jpg", [])]
};

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;
}
}

+ 5
- 5
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,8 +34,8 @@
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let ingredient of recipe.ingredients">
{{ ingredient.name }} - {{ ingredient.amount }}
*ngFor="let ingredient of (recipeState | async).recipes[id].ingredients">
{{ (recipeState | async).recipes[id].name }} - {{ (recipeState | async).recipes[id].amount }}
</li>
</ul>
</div>

+ 14
- 8
src/app/recipes/recipe-detail/recipe-detail.component.ts 查看文件

@@ -1,11 +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',
@@ -13,25 +15,29 @@ import * as fromApp from '../../ngrx/app.reducers';
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,
constructor(private route: ActivatedRoute,
private router: Router,
private store: Store<fromApp.AppState>) { }
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.store.dispatch(new ShoppingListActions.AddIngredients(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() {
@@ -39,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>


+ 11
- 19
src/app/recipes/recipe-list/recipe-list.component.ts 查看文件

@@ -1,38 +1,30 @@
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
- 19
src/app/recipes/recipe.service.ts 查看文件

@@ -16,23 +16,4 @@ export class RecipeService {
getRecipes() {
return this.recipes.slice();
}

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

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());
}
}

+ 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 {


+ 1
- 1
src/app/shared/data-storage.service.ts 查看文件

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

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


Loading…
取消
儲存