【问题标题】:How to know when item has been rendered in DOM using *ngFor?如何知道何时使用 *ngFor 在 DOM 中呈现项目?
【发布时间】:2019-06-07 18:09:04
【问题描述】:

我正在使用 *ngFor 显示消息列表,并希望将 css 类应用于添加到集合中的项目在列表最初呈现之后

即我不想在视图最初加载时应用该类,但仅在 messages 集合获得新项目时应用。

这个集合的来源是一个可以随时改变的服务的 observable。

<div *ngFor="let message of thread.messages">
    <div [class.fade-in-text]="threadWasLoaded">
        {{ message.text }}
    </div>
</div>

我以为我知道线程已加载后我会设置一个变量,但这没有奏效。我尝试在基本上每个 Angular 生命周期钩子中订阅 observable。

结果要么始终将 css 类应用于所有列表项,要么出现以下错误:

错误:ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。


this.thread$ = 
    this.storeQuery.getThreadWithMessages(this.threadId)
        .pipe(
            map(t => this.thread = new Thread(t)),
            tap(t => this.threadWasLoaded = true)
        );

我可能完全问错了问题。请让我知道我的整体方法是否偏离了基础。 :-)

编辑: 我从This Article 找到了解决我的问题的方法,它通过在页面加载时抑制子动画以不同的方式解决这个问题。

【问题讨论】:

  • 您能否以良好的方式访问初始数据?在初始对象上映射一个真正的布尔值怎么样 - 当它没有添加到稍后出现的对象时,该属性将是未定义的。然后你可以使用 ngClass ????为避免表达式错误,请使用 ngIf 删除 div 直到设置初始数据。
  • 直接修改 DOM 几乎总是离谱。 Angular 的目的是为你修改 DOM。我建议向您的对象添加一个属性,以便您可以通过 Angular 应用该类。如果您正在尝试这样做,您也可以查看动画。
  • 感谢大家的建议。我现在有时间回到这个话题。 @LarsRødal - 我确实可以访问初始数据,所以我尝试将属性应用于对象。问题是,为了设置正确的值(真或假),我需要知道该项目是否已经在 DOM 中呈现,我无法确定。
  • @theMayer - 我不打算直接操纵 dom。将属性应用于原始项目会很棒,我只是还没有弄清楚如何确定项目是否已被渲染到 DOM 尚未设置适当的值。
  • 项目在lifecycle的某个阶段渲染到DOM -

标签: angular rxjs rxjs6


【解决方案1】:

我知道这是通过另一种方式解决的,但对于未来的发现者来说,这是 pairwise() 运算符执行增量操作的完美用例:

  public  thread$ = this.dataService.thread$.pipe(
    startWith(null), // pairwise requires 2 emissions so we start it with a null
    pairwise(), // emit the last value and the current value in pairs
    map(([oThread, nThread]) => {
      if (oThread) { // only do this if there was a prior emission
        nThread.messages = nThread.messages.map(message => { // map the messages
          let isNew = false;
          if (!oThread.messages.find(m => m.id === message.id)) {
            // if they're not in the old array, they're new
            isNew = true;
          };
          return Object.assign({},message, {isNew});
        });
      }
      return nThread.messages;
    })
  );

闪电战:https://stackblitz.com/edit/angular-problem-preventing-animation-on-page-load-aybwz4?file=src/app/app.component.ts

【讨论】:

  • 好..!我是 rxjs 的新手,所以谢谢你..! :-)
【解决方案2】:

编辑 29/7-19: https://stackblitz.com/edit/angular-ptnvro 基于 cmets 更新了 Stackblitz。 :)

我根据我的评论为您创建了一个有效的 Stackblitz 示例: https://stackblitz.com/edit/angular-nn5w2u

这里我省略了生成名字的函数,但它在Stackblitz中。 对象 = [];

  ngAfterViewInit() {
    this.fetchData();
    interval(5000).subscribe(() => this.addItem());

  }

  fetchData() {
    // creates mock data with a delay, simulating backend
    timer(2000).subscribe(() => this.objects = [{name: 'John Doe', initial: true}, {name: 'Marilyn Monroe', initial: true}])
  }

  addItem() {
    this.objects.push({name: this.generateName()});
  }

-

<ng-container *ngIf="objects.length">
<p class *ngFor="let obj of objects" [class.newItem]="!obj.initial">
  {{obj.name}}
</p>
</ng-container>

【讨论】:

  • 感谢您的努力。在您的示例中,这是有效的,因为您将 initial 属性硬编码到项目中。就我而言,我有一个 observable,它有一个包含所有项目的数组属性。当 observable 发出一个新值时,我得到了完整的集合并且没有办法确定什么是新的。这就是为什么我试图简单地设置一个全局布尔值来指示项目是否已经被渲染。这样我就可以在该点之后始终设置正确的值。
  • 使用 Object.assign 将完整集合从 observable 复制到现有数据对象上,我的解决方案应该仍然有效 - 初始加载的元素将布尔值设置为 true(只需将其映射到初始加载,当数据对象未定义时),而稍后出现的元素将没有此键。 ? 如果您不明白,我可能会更新示例。 ?
  • 我不确定这是否可行,但我创建了一个 StackBlitz 来说明该问题。此外,我不再需要解决方案,因为我发现 This Article 通过抑制页面加载时的子动画以不同的方式解决此问题。
  • stackblitz.com/edit/angular-ptnvro - 在这里,按预期工作。可能对其他情况有用。 :)
【解决方案3】:

fade-in-text 类是动画吗?如果旧项目保留它们的fade-in-text 类,会导致问题吗?你能解决这个问题吗?

如果是这种情况,只需在所有项目上保留 fade-in-text 类。只有新项目才会获得动画。但是,如果您的 ngFor 中没有 trackBy,Angular 可能不知道一个项目是否是新的,因此它可能会删除/添加回旧项目的类,重新启动它们的淡入动画。

【讨论】:

  • 是的,fade-in-text 是动画!保持该等级的旧物品不是问题。我试图利用 trackBy 函数并将动画应用于所有项目,但是,我不希望它在页面加载时最初应用于项目,所以我正在寻找一种方法来确定是否有列表项实际上已经被渲染了。在那之后,我知道所有未来的项目都应该得到那个类。
【解决方案4】:

创建一个带有超时的可观察对象。

<div *ngFor="let message of thread.messages">
    <div [class.fade-in-text]="delayTrue() | async">
        {{ message.text }}
    </div>
</div>

function delayTrue() {
   return new Observable(s => setTimeout(() => (s.next(true),s.complete()));
}

删除您对threadWasLoaded 所做的所有操作,错误就会消失。

【讨论】:

  • 谢谢。我很难按原样工作。 TS 抱怨缺少括号。尝试像这样玩它:return new Observable(s =&gt; setTimeout(() =&gt; {s.next(true);s.complete();})); 这应该工作吗?我看到了同样的行为。 :-\
猜你喜欢
  • 2018-06-24
  • 2019-11-21
  • 2022-01-10
  • 1970-01-01
  • 2019-10-29
  • 2018-11-26
  • 1970-01-01
  • 2019-04-25
  • 1970-01-01
相关资源
最近更新 更多