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
相关产品推荐
相关产品推荐

