Angular Ngrx存储数据未显示

时间:2018-07-30 11:10:21

标签: angular typescript ngrx ngrx-store ngrx-effects

我遵循了许多教程来创建以下项目:我想显示从API调用获得的食物菜单。您可能会注意到一些其他未使用或以旧方式实现的功能。

我的主要重点是解决Search Food方法的问题,该方法应该使API调用检索数据并将其存储在Menu[]中。菜单中的数据是我想要在应用程序中列出的数据。

JSON:

[
  {
    "id": 1,
    "name": "burger",
    "description": "del delddd",
    "price": 10
  },
  {
    "id": 2,
    "name": "sandwich",
    "description": "del del",
    "price": 12
  },
  {
    "id": 3,
    "name": "fries",
    "description": "dl",
    "price": 3
  }
]

reducer.ts

import { Food } from '../models/food.model';
import { Menu } from '../models/menu.model';

// importing Actions
import * as FoodActions from './menu.action';

export interface State {
    loading: boolean;
    results: Menu[];
    selectedFood: Food;
    foodList: Food[];
};

const initialState: State =  {
    loading: false,
    results: [],
    selectedFood: null,
    foodList: []
}

export function reducer(state = initialState, action: FoodActions.Actions): State {
    switch (action.type) {
        case FoodActions.SEARCH: {
            return {
                ...state,
                loading: true
            }
        }
        case FoodActions.SEARCH_DONE: {
            return {
                ...state,
                loading: false,
                results: action.payload.menu
            }
        }
        case FoodActions.FETCH_FOOD: {
            return {
                ...state,
                loading: true
            }
        }
        case FoodActions.FETCH_FOOD_DONE: {
            return {
                ...state,
                loading: false,
                selectedFood: action.payload
            }
        }
        case FoodActions.ADD_FOOD: {
            return {
                ...state,
                foodList: [...state.foodList, state.selectedFood]
            }
        }
        case FoodActions.GET_FOOD: {
            return {
                ...state,
                selectedFood: state.foodList[action.payload]
            }
        }


        default: {
            return state;
        }

    }
}

menu.action.ts

import { Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { Food } from '../models/food.model';
import { Menu } from '../models/menu.model';

// True while fetching data from API
export const LOADING = 'Food Load';

// Searching Food via Food Search API
export const SEARCH = 'Food Search';
export const SEARCH_DONE = 'Food Search Done';

// Fetching Food Details via Food Report API
export const FETCH_FOOD = 'Fetch Food';
export const FETCH_FOOD_DONE = 'Fetch Food Done';

// Adding Food to My-Food-list
export const ADD_FOOD = 'Add Food';

// Getting Food Details from My-Food-list
export const GET_FOOD = 'Get Food';

// Removing Food to My-Food-list
export const REMOVE_FOOD = 'Remove Food';



export class Search implements Action {
    readonly type = SEARCH;
    constructor() { };
}

export class SearchDone implements Action {
    readonly type = SEARCH_DONE;
    constructor(public payload: {menu:Menu[]}) { };
}

export class FetchFood implements Action {
    readonly type = FETCH_FOOD;
    constructor(public payload: string) {}
}

export class FetchFoodDone implements Action {
    readonly type = FETCH_FOOD_DONE;
    constructor(public payload: Food) {}
}

export class AddFood implements Action {
    readonly type = ADD_FOOD;
    constructor() {}
}

export class GetFood implements Action {
    readonly type = GET_FOOD;
    constructor(public payload: string) {}
}

export class RemoveFood implements Action {
    readonly type = REMOVE_FOOD;
    constructor(public payload: Food) {}
}


export type Actions =   Search | SearchDone 
                        | AddFood | RemoveFood 
                        | FetchFood | FetchFoodDone 
                        | GetFood;

effects.ts

    import { Injectable } from '@angular/core';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable } from "rxjs";
import { map, take, switchMap } from 'rxjs/operators';
import { Menu } from './../models/menu.model'
import 'rxjs/add/operator/map';


import * as FoodActions from "./menu.action";
import { FoodService } from "../services/food.service";



@Injectable()

export class FoodEffects {

constructor(private actions$: Actions, 
    private foodService: FoodService) {}    

// Listen for the 'SEARCH' action
 @Effect() 
searchFood$: Observable<Action> = this.actions$.pipe(

 ofType<FoodActions.Search>(FoodActions.SEARCH),
 switchMap(() => {
    return this.foodService.searchFood()
 .pipe(map((results: Menu[]) => new FoodActions.SearchDone({menu: results}))
                // catch(() => of(new FoodActions.FetchFoodFail()))
 );
})
);

@Effect() 
fetchFood$: Observable<Action> = this.actions$
 // Listen for the 'FETCH_FOOD' action
.ofType(FoodActions.FETCH_FOOD)
.map((action: FoodActions.FetchFood) => action.payload)
.switchMap(query => {
    return this.foodService.fetchFood(query)
    .map(food => new FoodActions.FetchFoodDone(food));
    // catch(() => of(new FoodActions.FetchFoodFail()))
});
}

型号 menu.model.ts

    export interface IMenu{
    id: number;
    name: string;
    price: number;

}

export class Menu implements IMenu {
    id: number;
    name: string;
    price: number;

}

food.model.ts

export interface FoodItem{
    id: string;
    name: string;
    price: string;
}

export class Food implements FoodItem{
    id: string;
    name: string;
    price: string;
    constructor(obj?: any){
        this.id = obj.id || '';
        this.name = obj.ndbno || '';
        this.price = obj.price || '';
    }
}

服务 food.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from "rxjs/Rx";
import { map, take } from 'rxjs/operators';
import { catchError } from 'rxjs/operators';

import { Food } from '../models/food.model';
import { Menu } from '../models/menu.model';
@Injectable()
export class FoodService {
  // apiKey: string;

  constructor(private http: Http) { 
    // this.apiKey = 'XdagYlZrqyQtcLVstZl5fleZfzgFqK3ukMtWkPpZ';

  }

  searchFood(): Observable<Menu[]> {
    const url = 'https://my-json-server.typicode.com/waelhosn/Circular/menu';

    return this.http.get(url)
                        // ...and calling .json() on the response to return data
                         .map((res:Response) => {return res.json()})

                         //...errors if any
                         .catch((error:any) => Observable.throw(error.json().error || 'Server error'));


  }

  fetchFood(query: string): Observable<Food> {
    const url = 'https://my-json-server.typicode.com/waelhosn/Circular/menu';

    return this.http.get(url)
    .pipe(map(this.extractData))
    .pipe(catchError(this.handleError))
  }

  private extractData(res: Response): Food {
    const body = res.json().report.foods[0];
    return new Food(body);
  }

  private handleError(error: Response | any) {
    let errorMsg: string;
    if(error instanceof Response) {
      const body = error.json() ||  '';
      const err = body.error ||  JSON.stringify(body);
      errorMsg = err;
    } else {
      errorMsg = error.message ? error.message : error.toString();
    }
    return Observable.throw(errorMsg);
  }

  }

store.service.ts

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { Food } from '.././models/food.model';
import { Menu } from '.././models/menu.model';
import { FoodService } from '.././services/food.service';
import { Store } from "@ngrx/store";
import * as fromRoot from ".././actions/reducer";
import * as Actions from '.././actions/menu.action';

@Injectable()
export class StoreService {

  state: Observable<fromRoot.State>;
  constructor(private foodService: FoodService, private store: Store<fromRoot.State>) { 
  this.state = this.store;
  }


  searchFood() {
    this.store.dispatch(new Actions.Search());
  }

  fetchFood(id) {
    this.store.dispatch(new Actions.FetchFood(id));
  }

  getFood(id) {
    this.store.dispatch(new Actions.GetFood(id));
  }

  addBasket() {
    this.store.dispatch(new Actions.AddFood());
  }

  removeBasket(food: Food) {
    this.store.dispatch(new Actions.RemoveFood(food));
  }

}

组件

search-results.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { Menu } from '../models/menu.model';
import { Observable } from 'rxjs/Observable';
import { AppState } from './../app.state';
import { Store } from '@ngrx/store';
import * as fromRoot from '../actions/reducer';
import * as Actions from '../actions/menu.action';

@Component({
  selector: 'app-search-results',
  templateUrl: './search-results.component.html',
  styleUrls: ['./search-results.component.css']
})
export class SearchResultsComponent implements OnInit {

  results: Observable<Menu[]>;
  loading: Observable<Boolean>;

  constructor( private store: Store<AppState>) {
    this.results = this.store.select(state => state.menu);
    this.loading = this.store.select(state => state.loading);
   }

  ngOnInit() {
  }

}

search-results.component.html

<div fxLayout="column" fxLayoutAlign="center">
    <div class="subheader">
      <span style="font-weight:500">Results</span>
    </div>
    <mat-progress-bar *ngIf="loading | async" mode="indeterminate"></mat-progress-bar>
    <div class="search-results">
      <mat-list>
        <mat-list-item class="search-result-item" *ngFor="let result of results$ | async" [routerLink]="['/search', result.id]">
          <span class="search-result-item-name">{{result.name}}</span>
        </mat-list-item>
      </mat-list>
    </div>
  </div>

search-input.component.ts

import { Component, OnInit,ViewChild, ElementRef } from '@angular/core';
import { FormControl } from "@angular/forms";
import { Observable, BehaviorSubject } from 'rxjs';
import { Store } from '@ngrx/store';
import * as fromRoot from '../actions/reducer';
import * as Actions from '../actions/menu.action';
import 'rxjs/add/operator/filter'

import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/switch';
import { AppState } from '../app.state';

@Component({
  selector: 'app-search-input',
  templateUrl: './search-input.component.html',
  styleUrls: ['./search-input.component.css']
})
export class SearchInputComponent implements OnInit {


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

    ngOnInit() { 
      this.store.dispatch(new Actions.Search());
    }

}

search-input.component.html(下面的代码将被忽略,因为我不会使用搜索输入。我希望结果直接显示)

<div class="search-input-container">
    <input type="search" class="search-input"  mdInput placeholder="Search foods..." [routerLink]="['/search']">
</div>

链接到StackBlitz:https://stackblitz.com/edit/angular-ghtroq

0 个答案:

没有答案