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

Angular任务筛选:输入字符删除时筛选失效及最佳实践

问题描述

我正在开发一款Angular应用,需要根据任务的标题或描述进行筛选。输入字符时筛选功能运行正常,但删除字符(例如输入"create"后删除"reate")时,筛选结果无法正确更新。请问实现此类筛选功能有哪些最佳实践?

现有代码

main.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [FormsModule],
  template: `
  <input type="text" name="task-filter" (keyup)="onKeyUp($event)" [(ngModel)]="searchValue">
  @for (task of tasks ;track task.id) {
        <div class="task">
          <div>{{task.title}}</div>
          <div>{{task.description}}</div>
        </div>
      }
  `,
})
export class App implements OnInit {
  name = 'Angular';
  tasks!: Task[];
  tasksSubscription!: Subscription;
  searchValue: string = '';

  constructor(public taskService: TaskService) {}

  ngOnInit() {
    this.tasksSubscription = this.taskService.tasks$.subscribe((tasks) => {
      this.tasks = tasks;
    });
  }

  public onKeyUp(event: KeyboardEvent): void {
    this.taskService.filterTasks(this.searchValue);
  }

  ngOnDestroy(): void {
    this.tasksSubscription.unsubscribe();
  }
}

task-service.ts

export class TaskService {
  private _tasks$: BehaviorSubject<Task[]> = new BehaviorSubject<Task[]>([]);
  constructor() {
    this.fetchTasks();
  }

  public fetchTasks() {
    const tasks: Task[] = [
      {
        id: 1,
        title: 'Create Project Plan',
        description:
          'Develop a detailed project plan for the new software project.',
        category: 'Management',
        status: 'In Progress',
      },
      {
        id: 2,
        title: 'Conduct Code Review',
        description:
          'Perform a code review for the recently implemented feature.',
        category: 'Development',
        status: 'Pending',
      },
    ];
    this._tasks$.next(tasks);
  }

  public get tasks$(): Observable<Task[]> {
    return this._tasks$.asObservable() as Observable<Task[]>;
  }

  public get tasks(): Task[] {
    return this._tasks$.getValue() as Task[];
  }

  public filterTasks(searchText: string): void {
    if (!searchText) {
      this._tasks$.next(this.tasks);
    }

    searchText = searchText.toLowerCase();

    const filteredTasks = this.tasks.filter(
      (task) =>
        task.title.toLowerCase().includes(searchText) ||
        task.description.toLowerCase().includes(searchText)
    );

    this._tasks$.next(filteredTasks);
  }
}

问题原因分析

当前实现的核心问题是筛选时基于已过滤后的数据集,而非原始完整数据。每次调用filterTasks时,this.tasks返回的是_tasks$当前的最新值——第一次筛选后,_tasks$已经是过滤后的结果,当删除字符需要扩大筛选范围时,只能基于这个缩小后的列表去筛选,自然无法恢复原始的完整任务数据。

最佳实践与解决方案

1. 维护原始数据集,始终基于原始数据筛选

在服务中单独保存一份原始任务列表,所有筛选操作都从这份原始数据出发,避免污染源数据。

修改后的task-service.ts:

export class TaskService {
  private _tasks$: BehaviorSubject<Task[]> = new BehaviorSubject<Task[]>([]);
  private originalTasks: Task[] = []; // 存储原始完整任务列表

  constructor() {
    this.fetchTasks();
  }

  public fetchTasks() {
    const tasks: Task[] = [
      {
        id: 1,
        title: 'Create Project Plan',
        description:
          'Develop a detailed project plan for the new software project.',
        category: 'Management',
        status: 'In Progress',
      },
      {
        id: 2,
        title: 'Conduct Code Review',
        description:
          'Perform a code review for the recently implemented feature.',
        category: 'Development',
        status: 'Pending',
      },
    ];
    this.originalTasks = tasks; // 保存原始数据
    this._tasks$.next(tasks);
  }

  public get tasks$(): Observable<Task[]> {
    return this._tasks$.asObservable();
  }

  public filterTasks(searchText: string): void {
    let filteredTasks = [...this.originalTasks]; // 从原始数据开始筛选

    if (searchText) {
      const lowerSearchText = searchText.toLowerCase();
      filteredTasks = filteredTasks.filter(task => 
        task.title.toLowerCase().includes(lowerSearchText) || 
        task.description.toLowerCase().includes(lowerSearchText)
      );
    }

    this._tasks$.next(filteredTasks);
  }
}

2. 采用响应式编程风格,用RxJS操作符自动处理筛选

结合RxJS的操作符(如switchMap、debounceTime),将搜索输入和任务数据流结合,实现自动、高效的筛选,同时避免手动订阅的繁琐。

修改后的组件代码:

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [FormsModule],
  template: `
    <input type="text" name="task-filter" [(ngModel)]="searchValue" (ngModelChange)="searchValue$.next($event)">
    @for (task of filteredTasks$ | async; track task.id) {
      <div class="task">
        <div>{{task.title}}</div>
        <div>{{task.description}}</div>
      </div>
    }
  `,
})
export class App implements OnInit, OnDestroy {
  name = 'Angular';
  searchValue: string = '';
  private searchValue$ = new BehaviorSubject<string>('');
  filteredTasks$!: Observable<Task[]>;

  constructor(public taskService: TaskService) {}

  ngOnInit() {
    this.filteredTasks$ = this.searchValue$.pipe(
      debounceTime(300), // 防抖300ms,避免频繁触发筛选
      switchMap(searchText => 
        this.taskService.tasks$.pipe(
          map(tasks => {
            if (!searchText) return tasks;
            const lowerText = searchText.toLowerCase();
            return tasks.filter(task => 
              task.title.toLowerCase().includes(lowerText) || 
              task.description.toLowerCase().includes(lowerText)
            );
          })
        )
      )
    );
  }

  ngOnDestroy(): void {
    this.searchValue$.complete();
  }
}

对应的简化版task-service.ts(无需手动筛选方法):

export class TaskService {
  private _tasks$: BehaviorSubject<Task[]> = new BehaviorSubject<Task[]>([]);
  public tasks$: Observable<Task[]> = this._tasks$.asObservable();

  constructor() {
    this.fetchTasks();
  }

  public fetchTasks() {
    const tasks: Task[] = [
      {
        id: 1,
        title: 'Create Project Plan',
        description:
          'Develop a detailed project plan for the new software project.',
        category: 'Management',
        status: 'In Progress',
      },
      {
        id: 2,
        title: 'Conduct Code Review',
        description:
          'Perform a code review for the recently implemented feature.',
        category: 'Development',
        status: 'Pending',
      },
    ];
    this._tasks$.next(tasks);
  }
}

3. 使用Async管道简化订阅管理

组件中通过async管道直接订阅Observable,Angular会自动处理订阅和取消订阅,减少内存泄漏风险,代码更简洁。

4. 添加防抖优化

通过debounceTime操作符设置防抖时间(如300ms),避免用户每输入一个字符就触发一次筛选,在数据量较大时能显著提升性能。

5. 统一大小写处理

筛选时将搜索文本和任务的标题/描述统一转为小写(或大写),确保筛选不区分大小写,结果更准确。


内容的提问来源于stack exchange,提问作者coder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 14:04:54