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

Angular点击卡片触发randomSize()重渲染 瓦片高度随机变动问题

问题根因

Angular默认开发模式下,每次触发变更检测(点击事件、异步回调、路由变更等都会触发),都会重新执行模板中绑定的所有方法、表达式,校验返回值是否发生变化。你当前直接在模板的[maxHeight]绑定中调用randomSize()方法,每次变更检测运行时,该方法都会重新生成新的随机值传给卡片组件,直接导致所有卡片高度无规律重置。
你在CardComponent中写的ngOnChanges判断firstChange的逻辑也存在缺陷:首次赋值后直接return,会拦截后续所有合法的输入属性更新,而且完全无法解决父组件每次传入新随机值的根本问题。
Angular模板绑定的核心原则是:不要在模板中调用返回值不稳定的方法(比如带随机数、依赖可变全局状态的方法),所有需要动态计算的绑定值,都要在数据初始化阶段计算完成后存入变量,模板仅绑定预计算好的固定值。

修复方案

1. 预生成每个卡片的固定高度

在父组件拿到分类列表数据时,就给每个分类项提前生成一次随机高度并缓存,避免每次变更检测重复计算。
修改DomainDetailsComponent.ts中的数据加载逻辑:

this.domain$ = this.domainService.getDomainById(this.domainId).pipe(map((res) => {
  let domain = res.data[0];
  if (domain && domain.categories?.length) {
    // 遍历分类,每个分类只生成一次随机高度并挂载到对象上
    domain.categories.forEach(category => {
      (category as any).fixedHeight = this.randomSize();
    });
    this.divideArray(domain.categories, domain.categories.length / 3);
  }
  return domain
}));

如果不想修改原有CategoryModel的类型定义,可以单独创建一个以分类ID为key的Map对象存储高度:

// 在类中声明映射表
cardHeightMap = new Map<number, number>();

// 数据加载时生成高度存入映射表
domain.categories.forEach(category => {
  this.cardHeightMap.set(category.id, this.randomSize());
});

2. 修改模板绑定,去掉方法调用

把模板中直接调用randomSize()的部分,替换为绑定预先生成好的固定高度值。
修改DomainDetailsComponent.html:

<div class="grid_row" *ngIf="catArray.length > 0">
  <ng-container *ngFor="let line of catArray">
    <div class="grid_column">
      <!-- 直接绑定预生成的固定高度 -->
      <app-card 
        *ngFor="let cat of line" 
        [category]="cat" 
        [maxWidth]="450" 
        [maxHeight]="cat.fixedHeight"
        <!-- 如果用映射表就写[maxHeight]="cardHeightMap.get(cat.id)" -->
      ></app-card>
    </div>
  </ng-container>
</div>

3. 清理CardComponent的冗余逻辑

@Input()装饰的属性本身会在父组件传值变化时自动更新,不需要手动在ngOnChanges中重新赋值,你当前写的ngOnChanges反而会拦截后续合法的属性更新,直接删除整个ngOnChanges方法即可。
清理后的CardComponent.ts核心代码:

import {Component, HostListener, Input, OnInit} from '@angular/core';
import {CategoryModel} from "../../models/CategoryModel";

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.scss']
})
export class CardComponent implements OnInit {
  @Input()
  category?: CategoryModel;
  @Input()
  descLimit?: number = 20;
  @Input()
  allowRedirect: boolean = true;
  @Input()
  maxWidth?: number = 250;
  @Input()
  maxHeight?: number = 300;

  constructor() {}
  ngOnInit(): void {}

  @HostListener("click", ["$event"])
  public onClick(event: any): void {
    if (!this.allowRedirect) event.stopPropagation();
  }
}
额外注意事项
  • 即便在生产环境下Angular变更检测只会执行一轮,也不推荐在模板中绑定复杂方法调用,会造成不必要的性能损耗,所有需要计算的绑定值都要提前在组件类中缓存。
  • 你当前用手动切分数组实现的多列瀑布流,在后续做响应式适配、无限加载、卡片排序时维护成本很高,可以考虑用成熟的瀑布流布局库实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 19:01:46