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

Angular 19信号API优化:避免变更检测时重复创建Computed Signals

优化Angular Signals菜单激活状态检测的方案

你当前的实现每次模板变更检测时,都会为每个菜单项创建新的computed信号实例,这会造成不必要的性能开销。以下是几种在保持URL响应式的前提下,避免重复创建computed的实用方案:

方案一:预缓存菜单项的computed信号

通过维护一个Map存储每个菜单项对应的computed实例,确保每个项只创建一次信号,适合菜单项动态变化的场景:

import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { toSignal, computed, ComputedRef } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter, map } from 'rxjs';

interface MenuItem {
  routerLink: string;
  label: string;
}

@Component({
  template: `
    <li *ngFor="let item of items">
      <a [routerLink]="item.routerLink" 
         [class.active]="activatedMap.get(item)()">
        {{ item.label }}
      </a>
    </li>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MenuComponent implements OnInit {
  url = toSignal(
    this.router.events.pipe(
      filter(e => e instanceof NavigationEnd),
      map(e => e.urlAfterRedirects)
    )
  );

  activatedMap = new Map<MenuItem, ComputedRef<boolean>>();
  private _items: MenuItem[] = [];

  @Input() set items(newItems: MenuItem[]) {
    this._items = newItems;
    this.updateActivatedMap();
  }

  get items(): MenuItem[] {
    return this._items;
  }

  constructor(private router: Router) {}

  ngOnInit() {
    this.updateActivatedMap();
  }

  private updateActivatedMap() {
    this.activatedMap.clear();
    this.items.forEach(item => {
      this.activatedMap.set(item, computed(() => {
        return this.url()?.startsWith(item.routerLink) ?? false;
      }));
    });
  }
}

方案二:单个computed生成所有激活状态映射

创建一个computed信号,返回包含所有菜单项激活状态的对象,只维护一个信号实例,代码更简洁:

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { toSignal, computed } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter, map } from 'rxjs';

interface MenuItem {
  routerLink: string;
  label: string;
}

@Component({
  template: `
    <li *ngFor="let item of items">
      <a [routerLink]="item.routerLink" 
         [class.active]="activatedStates()[item.routerLink]">
        {{ item.label }}
      </a>
    </li>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MenuComponent {
  url = toSignal(
    this.router.events.pipe(
      filter(e => e instanceof NavigationEnd),
      map(e => e.urlAfterRedirects)
    )
  );

  @Input() items: MenuItem[] = [];

  activatedStates = computed(() => {
    const currentUrl = this.url();
    return this.items.reduce((acc, item) => {
      acc[item.routerLink] = currentUrl?.startsWith(item.routerLink) ?? false;
      return acc;
    }, {} as Record<string, boolean>);
  });
}

方案三:模板中直接使用信号表达式

如果判断逻辑简单,可以直接在模板里使用url()信号的当前值进行判断,无需额外创建computed:

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { toSignal } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter, map } from 'rxjs';

interface MenuItem {
  routerLink: string;
  label: string;
}

@Component({
  template: `
    <li *ngFor="let item of items">
      <a [routerLink]="item.routerLink" 
         [class.active]="url()?.startsWith(item.routerLink) ?? false">
        {{ item.label }}
      </a>
    </li>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MenuComponent {
  url = toSignal(
    this.router.events.pipe(
      filter(e => e instanceof NavigationEnd),
      map(e => e.urlAfterRedirects)
    )
  );

  @Input() items: MenuItem[] = [];
}

这种方式开销低,适合简单场景,Angular会自动跟踪信号依赖,URL变化时更新所有相关绑定。

方案四:自定义指令封装激活逻辑

将激活状态判断封装成独立指令,每个指令实例只创建一次computed,便于跨组件复用:

import { Directive, Input, OnInit, ElementRef } from '@angular/core';
import { toSignal, computed, effect } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter, map } from 'rxjs';

@Directive({
  selector: '[menuItemActive]',
  standalone: true
})
export class MenuItemActiveDirective implements OnInit {
  @Input('menuItemActive') routerLink: string = '';

  url = toSignal(
    this.router.events.pipe(
      filter(e => e instanceof NavigationEnd),
      map(e => e.urlAfterRedirects)
    )
  );

  isActive = computed(() => this.url()?.startsWith(this.routerLink) ?? false);

  constructor(
    private router: Router,
    private elementRef: ElementRef<HTMLElement>
  ) {}

  ngOnInit() {
    effect(() => {
      this.elementRef.nativeElement.classList.toggle('active', this.isActive());
    });
  }
}

模板使用方式:

<li *ngFor="let item of items">
  <a [routerLink]="item.routerLink" 
     [menuItemActive]="item.routerLink">
    {{ item.label }}
  </a>
</li>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:03:15