You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 19:48:04