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

Angular中含routerOutlet的组件间传值问题求助

Angular 跨组件数据传递解决方案:Category → Products(路由加载场景)

问题背景

Dashboard 组件左侧为 Category 组件(带多选分类复选框),右侧通过 routerOutlet 加载 Products 组件。需要实现:选中分类后,将选中的分类 ID 传递给 Products 组件,用于筛选产品列表。

现有代码

dashboard.component.html

<div class="columns">
    <div class="column is-2">
        <app-category></app-category>
    </div>
    <div class="column is-10">
        <router-outlet ></router-outlet>
    </div>
</div>

category.component.html

<div class="container">
    <input type="checkbox" id="menu-toggle">
    <label for="menu-toggle" class="hamburger-menu" (click)="showCategoryList()"><span></span></label>
    <div class="content">
        <p class="menu-title title is-4" style="font-weight: normal">Categories</p>
        <ul class="menu-list">
            <li class="subtitle category-list" *ngFor="let category of categories">
                <input type="checkbox" [id]="category.id" (change)="filterProductsByCategory($event)">
                <label [htmlFor]="category.id">{{category.name}}</label>
            </li>
        </ul>
        <div class="filter mb-3"><button (click)="getFilterData()">Filter</button></div>
    </div>
</div>

category.component.ts

export class CategoryComponent implements OnInit{

  categories: Category[] = [];
  activatedCategory?: Category;
  dataLoaded = false;
  currentCategories: Category[] = [];
  selectedCategory: string[]  = []; // 修正类型:存储分类ID字符串

  constructor(private categoryService: CategoryService, private filterService: FilterService) { }

  ngOnInit(): void {
    this.getCategories();
  }

  getCategories() {
    this.categoryService.getCategories().subscribe(response => {
      this.categories = response.data;
      this.dataLoaded = true;
    })
  }
  
  getActivatedCategory(category: Category) {
    this.activatedCategory = category;
  }

  setCurrencyCategory(category: Category) {
    this.currentCategories.push(category);
  }

  showCategoryList() {
    const content = document.querySelector(".content") as HTMLElement;
    content.style.transition = "0.5s ease";
    content.style.display = content.style.display === "block" ? "none" : "block";
  }

  filterProductsByCategory(event: any){
    const categoryId = event.target.id;
    if(event.target.checked){
      this.selectedCategory.push(categoryId);
    } else {
      const index = this.selectedCategory.indexOf(categoryId);
      if(index !== -1) this.selectedCategory.splice(index, 1);
    }
  }

  getFilterData(){
    const parsedIds = this.selectedCategory.map(id => parseInt(id));
    this.filterService.setSelectedCategories(parsedIds);
  }
}

products.component.html

<div id="products">   
    <div *ngIf="!dataLoaded" class="lds-ring"><div></div><div></div><div></div><div></div></div>
    <h1 class="title">Products</h1>
    <div id="search-box">
      <input class="input mb-4" type="text" placeholder="Filter by product name" [(ngModel)]="searchText">
      <button class="button is-dark" type="button">Search</button>
    </div>
    
    <table *ngIf="dataLoaded && products" class="table is-hoverable is-fullwidth  is-striped" >
      <thead>
        <tr>
            <th>ID</th>
            <th>Product Name</th>
            <th>Category ID</th>
            <th>Price</th>
            <th>Unit In Stock</th>
            <th>Unit On Order</th>
            <th>Reorder Level</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let product of products | filterProducts: searchText">
          <td>{{ product.id }}</td>
          <td>{{ product.name }}</td>
          <td>{{ product.categoryID }}</td>
          <td>{{ product.unitPrice| currency: "TR" : '₺' : '2.2-2'  }}</td>
          <td>{{ product.unitsInStock }}</td>
          <td>{{ product.unitsOnOrder }}</td>
          <td>{{ product.reorderLevel }}</td>
        </tr>
    </table>
    <div *ngIf="!products">No Products!</div>
  </div>

products.component.ts

import { Subscription } from 'rxjs';

export class ProductsComponent implements OnInit, OnDestroy{

  products: Product[] = [];
  searchText = '';
  dataLoaded = false;
  private filterSubscription?: Subscription;

  constructor(
    private productService: ProductService, 
    private activatedRoute: ActivatedRoute,
    private filterService: FilterService
  ) { }

  ngOnInit(): void {
    // 监听筛选条件变化
    this.filterSubscription = this.filterService.getSelectedCategories().subscribe(categoryIds => {
      categoryIds.length > 0 ? this.getProductsByCategoryIds(categoryIds) : this.getProducts();
    });

    // 兼容原路由参数逻辑
    this.activatedRoute.params.subscribe(params => {
      if(params["id"]){
        this.filterService.setSelectedCategories([params["id"]]);
      }
    })
  }

  getProducts() {
    this.productService.getProducts().subscribe(response => {
        this.products = response.data;
        this.dataLoaded = true; 
    })
   }

   getProductsByCategoryIds(categoryIds: number[]) {
    // 前端筛选:若后端支持批量分类查询,替换为对应接口调用
    this.productService.getProducts().subscribe(response => {
      this.products = response.data.filter(p => categoryIds.includes(p.categoryID));
      this.dataLoaded = true;
    })
   }

   ngOnDestroy(): void {
    this.filterSubscription?.unsubscribe();
   }
}

核心解决方案:共享 Service(推荐)

针对路由加载的兄弟组件,使用 BehaviorSubject 创建单例共享服务是最便捷、低耦合的方案:

1. 创建 FilterService

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class FilterService {
  private selectedCategories$ = new BehaviorSubject<number[]>([]);

  setSelectedCategories(categoryIds: number[]): void {
    this.selectedCategories$.next(categoryIds);
  }

  getSelectedCategories(): Observable<number[]> {
    return this.selectedCategories$.asObservable();
  }
}

2. 关键修改说明

  • Category 组件:注入 FilterService,在点击「Filter」时将解析后的分类 ID 传递给服务。
  • Products 组件:订阅服务的分类 ID 流,根据接收的数组筛选产品;组件销毁时取消订阅,避免内存泄漏。
  • 修复原代码问题:修正 filterProductsByCategory 中的删除逻辑(原代码错误地将字符串当作对象处理)。

可选方案:URL QueryParams 传递

若需要将筛选条件暴露在 URL 中(支持刷新保留状态),可使用路由参数传递:

Category 组件修改

import { Router } from '@angular/router';

constructor(private router: Router, ...) {}

getFilterData(){
  const parsedIds = this.selectedCategory.map(id => parseInt(id));
  this.router.navigate(['/dashboard/products'], { queryParams: { categoryIds: parsedIds.join(',') } });
}

Products 组件修改

ngOnInit(): void {
  this.activatedRoute.queryParams.subscribe(params => {
    if(params['categoryIds']){
      const categoryIds = params['categoryIds'].split(',').map(id => parseInt(id));
      this.getProductsByCategoryIds(categoryIds);
    } else {
      this.getProducts();
    }
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:08:10