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

Angular作物与地区复选框筛选功能实现及问题解决

解决Angular中复选框筛选的重复显示与逻辑问题

看起来你遇到了两个核心问题:一是复选框因为数据重复而重复显示,二是筛选逻辑不符合需求。我会一步步帮你重构代码,实现你想要的功能。

问题根源分析

  1. 数据结构不合理:你的CROPS数组里有两个同名的"Rice"条目,直接遍历渲染会导致复选框重复。
  2. 筛选逻辑未区分层级:作物筛选应该是全局隐藏该作物的所有卡片,地区筛选只隐藏对应地区的该作物卡片,而你的当前逻辑没有区分这两种筛选的作用范围。

解决方案步骤

1. 重构数据模型与数据源

首先我们拆分数据结构,分离唯一的作物列表、地区列表,以及作物-地区的关联数据:

// crop.model.ts
export interface Crop {
  name: string;
  checked: boolean;
}

export interface District {
  name: string;
  checked: boolean;
}

export interface CropItem {
  cropName: string;
  districtName: string;
  subCategory: Subcategory[];
}

export interface Subcategory {
  id: number;
  name: string;
  checked: boolean;
}
// crop.data.ts
import { Crop, District, CropItem } from "./crop.model";

// 存储作物与地区的关联数据
export const CROP_ITEMS: CropItem[] = [
  {
    cropName: "Rice",
    districtName: "Thane",
    subCategory: [
      { id: 1, name: "Basmati", checked: true },
      { id: 2, name: "Ammamore", checked: true }
    ]
  },
  {
    cropName: "Rice",
    districtName: "Nashik",
    subCategory: [
      { id: 1, name: "Basmati", checked: true },
      { id: 2, name: "Ammamore", checked: true }
    ]
  },
  {
    cropName: "Wheat",
    districtName: "Nashik",
    subCategory: [
      { id: 1, name: "Durum", checked: true },
      { id: 2, name: "Emmer", checked: true }
    ]
  },
  {
    cropName: "Barley",
    districtName: "Ratnagiri",
    subCategory: [
      { id: 1, name: "Hulless Barley", checked: true },
      { id: 2, name: "Barley Flakes", checked: true }
    ]
  }
];

// 提取唯一作物列表并初始化选中状态
export const CROPS: Crop[] = Array.from(new Set(CROP_ITEMS.map(item => item.cropName)))
  .map(name => ({ name, checked: true }));

// 提取唯一地区列表并初始化选中状态
export const DISTRICTS: District[] = Array.from(new Set(CROP_ITEMS.map(item => item.districtName.trim())))
  .map(name => ({ name, checked: true }));

2. 修改服务层实现筛选逻辑

我们用BehaviorSubject来管理状态,自动处理筛选逻辑:

// crop.service.ts
import { Injectable } from "@angular/core";
import { Observable, BehaviorSubject } from "rxjs";
import { Crop, District, CropItem } from "../shared/crop.model";
import { CROPS, DISTRICTS, CROP_ITEMS } from "../shared/crop.data";

@Injectable({
  providedIn: "root"
})
export class CropService {
  private cropsSubject = new BehaviorSubject<Crop[]>(CROPS);
  private districtsSubject = new BehaviorSubject<District[]>(DISTRICTS);
  private cropItemsSubject = new BehaviorSubject<CropItem[]>(CROP_ITEMS);

  crops$ = this.cropsSubject.asObservable();
  districts$ = this.districtsSubject.asObservable();
  filteredCropItems$ = this.cropItemsSubject.asObservable();

  constructor() {
    this.applyFilters(); // 初始化筛选
  }

  // 更新作物选中状态
  updateCropChecked(name: string, checked: boolean) {
    const updatedCrops = this.cropsSubject.value.map(crop => 
      crop.name === name ? { ...crop, checked } : crop
    );
    this.cropsSubject.next(updatedCrops);
    this.applyFilters();
  }

  // 更新地区选中状态
  updateDistrictChecked(name: string, checked: boolean) {
    const updatedDistricts = this.districtsSubject.value.map(district => 
      district.name === name ? { ...district, checked } : district
    );
    this.districtsSubject.next(updatedDistricts);
    this.applyFilters();
  }

  // 核心筛选逻辑
  private applyFilters() {
    const activeCrops = this.cropsSubject.value.filter(c => c.checked).map(c => c.name);
    const activeDistricts = this.districtsSubject.value.filter(d => d.checked).map(d => d.name);

    // 过滤出同时满足作物和地区都选中的条目
    const filteredItems = CROP_ITEMS.filter(item => 
      activeCrops.includes(item.cropName) && activeDistricts.includes(item.districtName.trim())
    );

    this.cropItemsSubject.next(filteredItems);
  }
}

3. 更新组件逻辑

分离作物和地区的复选框事件处理:

// all-trades.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Crop, District, CropItem } from 'src/app/shared/crop.model';
import { CropService } from '../crop.service';

@Component({
  selector: 'app-all-trades',
  templateUrl: './all-trades.component.html',
  styleUrls: ['./all-trades.component.css'],
})
export class AllTradesComponent implements OnInit {
  crops$: Observable<Crop[]>;
  districts$: Observable<District[]>;
  filteredCropItems$: Observable<CropItem[]>;

  constructor(private cropService: CropService) { }

  ngOnInit(): void {
    this.crops$ = this.cropService.crops$;
    this.districts$ = this.cropService.districts$;
    this.filteredCropItems$ = this.cropService.filteredCropItems$;
  }

  onCropChange(event: any, crop: Crop) {
    this.cropService.updateCropChecked(crop.name, event.checked);
  }

  onDistrictChange(event: any, district: District) {
    this.cropService.updateDistrictChecked(district.name, event.checked);
  }
}

4. 修改模板渲染

使用唯一的作物/地区列表渲染复选框,直接遍历筛选后的结果渲染卡片:

<app-header></app-header>
<div fxLayout="row" fxLayout.lt-md="column" fxLayoutAlign="space-between start" fxLayoutAlign.lt-md="start stretch" >
  <div class="container-outer" fxFlex="20">
    <div class="filters">
      <section class="example-section">
        <span class="example-list-section">
          <h1>Select Crop</h1>
        </span>
        <span class="example-list-section">
          <ul>
            <li *ngFor="let crop of crops$ | async">
              <mat-checkbox [checked]="crop.checked" (change)="onCropChange($event, crop)" >
                {{ crop.name }}
              </mat-checkbox>
            </li>
          </ul>
        </span>
      </section>
      <section class="example-section">
        <span class="example-list-section">
          <h1>Select District</h1>
        </span>
        <span class="example-list-section">
          <ul>
            <li *ngFor="let district of districts$ | async">
              <mat-checkbox [checked]="district.checked" (change)="onDistrictChange($event, district)" >
                {{ district.name }}
              </mat-checkbox>
            </li>
          </ul>
        </span>
      </section>
    </div>
  </div>
  <div class="content container-outer" fxFlex="80">
    <mat-card class="crop-card" style="min-width: 17%" *ngFor="let item of filteredCropItems$ | async" >
      <a [routerLink]="[item.cropName]">
        <mat-card-header>
          <img mat-card-avatar class="example-header-image" src="/assets/icons/crops/{{ item.cropName }}.PNG" alt="crop-image" />
          <mat-card-title>{{ item.cropName }}</mat-card-title>
          <mat-card-subtitle>100 Kgs</mat-card-subtitle>
        </mat-card-header>
      </a>
      <mat-card-content>
        <p>PRICE</p>
      </mat-card-content>
      <mat-card-content>
        <p>{{ item.districtName }}</p>
      </mat-card-content>
    </mat-card>
  </div>
</div>
<app-footer></app-footer>

功能说明

  • 作物筛选:取消勾选"RICE"时,所有关联RICE的条目都会被过滤,对应的地区卡片全部隐藏。
  • 地区筛选:取消勾选"Thane"时,只有Thane地区的RICE条目会被过滤,其他地区的RICE卡片正常显示。
  • 复选框重复问题:通过提取唯一的作物和地区名称,彻底解决了重复显示的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 21:38:11