我遵循了许多教程来创建以下项目:我想显示从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