使用NgRx实现输入联想API调用功能代码报错,寻求解决方案
问题修复与完整实现方案
现有代码错误排查
- reducer未导出:
call.reducer.ts中createReducer返回的reducer函数没有导出,NgRx无法注册该feature reducer - 组件代码逻辑混乱:
app.component.ts重复定义了3个类,业务逻辑没有写在AppComponent中,也未注入Store依赖,queryTextChanged方法缺少闭合括号,未导入必要的RxJS操作符 - 状态设计不全:
ICall中没有存储联想提示词列表、加载状态、错误状态的字段 - 缺少副作用处理逻辑:调用第三方API属于异步副作用,需要使用NgRx Effects实现,现有代码完全没有相关逻辑
- 选择器文件路径错误:你创建的路径应该是
src/app/call/call.selectors.ts,和现有文件夹结构对应
完整可运行代码实现
1. 扩展状态定义 src/app/call/call.state.ts
export interface ICall { readonly query: string; readonly suggestions: string[]; // 存储联想提示词 readonly loading: boolean; // 接口加载状态 readonly error: string | null; // 接口错误信息 }
2. 补充Action定义 src/app/call/call.actions.ts
import { createAction, props } from '@ngrx/store'; export const searchQueryChanged = createAction( '[Search] Search query changed', props<{ query: string }>() ); export const loadSuggestionsSuccess = createAction( '[Search] Load suggestions success', props<{ suggestions: string[] }>() ); export const loadSuggestionsFailure = createAction( '[Search] Load suggestions failure', props<{ error: string }>() );
3. 修复Reducer src/app/call/call.reducer.ts
import { createReducer, on } from '@ngrx/store'; import { ICall } from './call.state'; import * as CallActions from './call.actions'; export const initialState: ICall = { query: '', suggestions: [], loading: false, error: null }; // 必须导出reducer供NgRx注册 export const callReducer = createReducer( initialState, on(CallActions.searchQueryChanged, (state, { query }) => ({ ...state, query, loading: true, error: null, suggestions: [] })), on(CallActions.loadSuggestionsSuccess, (state, { suggestions }) => ({ ...state, loading: false, suggestions })), on(CallActions.loadSuggestionsFailure, (state, { error }) => ({ ...state, loading: false, error })) );
4. 补充Selector定义 src/app/call/call.selectors.ts
import { createSelector, createFeatureSelector } from '@ngrx/store'; import { ICall } from './call.state'; export const callFeatureSelector = createFeatureSelector<ICall>('call'); export const getQuery = createSelector( callFeatureSelector, (state) => state.query ); export const getSuggestions = createSelector( callFeatureSelector, (state) => state.suggestions ); export const getLoading = createSelector( callFeatureSelector, (state) => state.loading ); export const getError = createSelector( callFeatureSelector, (state) => state.error );
5. 新增Effects处理API调用 src/app/call/call.effects.ts
import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of } from 'rxjs'; import { map, switchMap, catchError, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'; import * as CallActions from './call.actions'; import { HttpClient } from '@angular/common/http'; @Injectable() export class CallEffects { loadSuggestions$ = createEffect(() => this.actions$.pipe( ofType(CallActions.searchQueryChanged), // 防抖300ms,避免输入过快频繁调用接口 debounceTime(300), // 输入内容不变时不重复调用 distinctUntilChanged((prev, curr) => prev.query === curr.query), // 空输入不调用接口 filter(action => action.query.trim().length > 0), switchMap(action => { // 替换为你实际要调用的第三方公开API地址 const apiUrl = `https://api.example.com/search/suggest?q=${encodeURIComponent(action.query)}`; return this.http.get<string[]>(apiUrl).pipe( map(suggestions => CallActions.loadSuggestionsSuccess({ suggestions })), catchError(error => of(CallActions.loadSuggestionsFailure({ error: error.message }))) ); }) ) ); constructor( private actions$: Actions, private http: HttpClient ) {} }
6. 配置AppModule src/app/app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { AppComponent } from './app.component'; import { callReducer } from './call/call.reducer'; import { CallEffects } from './call/call.effects'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, // 注册feature reducer StoreModule.forRoot({ call: callReducer }), // 注册Effects EffectsModule.forRoot([CallEffects]), // 开发环境可添加DevTools方便调试 StoreDevtoolsModule.instrument({ maxAge: 25 }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
7. 修复AppComponent src/app/app.component.ts
import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { searchQueryChanged } from './call/call.actions'; import { getQuery, getSuggestions, getLoading, getError } from './call/call.selectors'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'ngrx-ex-api'; query$: Observable<string>; suggestions$: Observable<string[]>; loading$: Observable<boolean>; error$: Observable<string | null>; constructor(private store: Store) { this.query$ = this.store.select(getQuery); this.suggestions$ = this.store.select(getSuggestions); this.loading$ = this.store.select(getLoading); this.error$ = this.store.select(getError); } queryTextChanged(query: string) { this.store.dispatch(searchQueryChanged({ query })); } }
8. 完善模板 src/app/app.component.html
<div class="search-container"> <input [value]="query$ | async" (input)="queryTextChanged($any($event.target).value)" placeholder="请输入搜索内容" /> <!-- 加载状态提示 --> <div *ngIf="loading$ | async">加载中...</div> <!-- 错误提示 --> <div *ngIf="error$ | async as error" style="color: red;">{{ error }}</div> <!-- 联想提示列表 --> <ul class="suggestions" *ngIf="(suggestions$ | async)?.length"> <li *ngFor="let suggestion of suggestions$ | async"> {{ suggestion }} </li> </ul> </div>
注意事项
- 替换
call.effects.ts中的API地址为你实际要调用的第三方公开API,注意处理跨域问题,如果存在跨域可以配置Angular代理或者使用支持CORS的API - 可以根据需求调整防抖时间,当前设置为300ms
- 如果不需要留存历史搜索状态,也可以直接在组件内处理API调用,不用全量走NgRx,当前实现符合NgRx最佳实践,状态统一管理便于后续扩展
内容的提问来源于stack exchange,提问作者tearex
相关产品推荐
相关产品推荐

