ngAfterViewInit( ) to early /// ngFor和异步网络请求导致无法选中目标元素的问题

今天遇到的最无语的问题:

angular 8版本以上,我在ngafterViewInit()中选取元素选取不到,加上setTimeOut()之后才可以选中

网上的解释:

Angular的ngAfterViewInit生命周期钩子是在组件视图的子视图初始化完成之后被调用的。但是如果您遇到了在ngAfterViewInit中某些元素仍未完全初始化的问题,可能是因为这些元素在ngAfterViewInit被调用时还没有完成初始化。

这通常是因为您的组件视图包含异步操作,例如使用*ngIf*ngForsetTimeout等等。这些操作会导致元素的延迟初始化,并且可能会影响到ngAfterViewInit的调用时机。

为了解决这个问题,您可以考虑使用ngAfterViewChecked生命周期钩子。ngAfterViewChecked会在每次组件视图检查完成之后被调用,因此它可以确保在视图元素完全初始化之后再执行某些操作。

但需要注意的是,使用ngAfterViewChecked可能会导致性能问题,因为它会在每次变更检测周期中被调用,这可能会增加应用程序的负载。

总之,如果您遇到了ngAfterViewInit中某些元素还未完成初始化的问题,可以尝试使用ngAfterViewChecked或者调整您的代码,以确保异步操作在视图元素完成初始化之后再执行。

结果代码检查,确实是使用了 *ngfor 的嵌套导致,并且使用setTimeout 之后可解决。

但是

前端体验不好,有明显延迟,

并且尝试使用ngAfterViewChecked后出现严重性能问题,当我使用 RXJS进行消息流通知时,出现了无限次调用。

ngAfterViewInit() 生命周期钩子在 Angular 渲染视图之前就会被触发,此钩子函数不是应该在视图渲染完毕后触发吗

ngAfterViewInit是Angular中的一个生命周期钩子函数,它会在组件的视图初始化完成后被调用。也就是说,当组件的模板和视图组成的DOM树渲染完成并且所有子组件都已经初始化完成后,ngAfterViewInit函数会被触发。

至于异步操作是否会影响ngAfterViewInit函数的调用,答案是:取决于异步操作的具体实现方式。如果异步操作是通过Angular的异步服务(例如HttpClient)来实现的,那么它们不会影响ngAfterViewInit函数的调用。

但是,如果使用了JavaScript原生的异步API(例如setTimeoutsetInterval),那么它们可能会在视图初始化完成之前就被触发,从而导致ngAfterViewInit函数无法正常执行。

核心原因:

这是因为 Angular 在渲染视图时使用了单向数据绑定和变更检测机制,这些机制依赖于视图已经完全初始化。如果在视图尚未完全初始化时尝试访问元素或进行其他操作,可能会导致错误或不一致的行为。因此,我们需要确保在视图初始化完成后再进行任何操作。

当 Angular 渲染组件视图时,它会执行以下步骤:

1. Angular 解析组件模板,并根据模板中的指令和数据绑定创建 DOM 元素。
2. Angular 将这些 DOM 元素插入到文档中,并将其与组件实例关联起来。
3. Angular 启动变更检测机制,以便在组件状态发生变化时更新视图。

在这个过程中,Angular 会使用单向数据绑定和变更检测机制来确保组件状态和视图保持同步。这意味着,在视图初始化完成之前,组件状态可能会发生变化,但这些变化不会立即反映在视图中。

如果您在 `ngAfterViewInit()` 生命周期钩子中尝试访问元素或进行其他操作,而这些操作需要在视图初始化完成后才能执行,那么可能会导致错误或不一致的行为。例如,如果您尝试在视图初始化完成之前获取元素的引用,那么该元素可能尚未被创建,从而导致访问错误或返回 `null`。

在 Angular 中,组件状态指的是组件类中的属性和方法。当组件状态发生变化时,这些变化可能会影响组件的行为或外观。例如,当用户单击按钮时,组件可能会将某个属性的值更改为新值。

与此同时,视图是与组件状态相关联的。当组件状态发生变化时,Angular 会使用变更检测机制来检测这些变化,并更新视图以反映这些变化。这意味着,当组件状态发生变化时,视图也应该随之更新。

但是,在视图初始化完成之前,组件状态可能会发生变化,但这些变化不会立即反映在视图中。这是因为 Angular 的变更检测机制需要一些时间来检测状态变化并更新视图。在这段时间内,视图可能会显示旧的状态,而不是最新的状态。

例如,假设您有一个组件,其中包含一个按钮和一个计数器属性。当用户单击按钮时,计数器属性的值会增加。如果您在 `ngAfterViewInit()` 生命周期钩子中尝试访问计数器属性的值,那么您可能会发现该值仍然是旧的值,而不是最新的值。这是因为视图尚未完全初始化,变更检测机制尚未检测到计数器属性的变化。

为了确保在视图初始化完成后访问最新的组件状态,请使用 `setTimeout()` 函数将操作推迟到下一个 JavaScript 事件循环中。这样可以确保 Angular 的变更检测机制已经完成,并且视图已经更新以反映最新的组件状态。

为了避免这种情况,您可以使用 `setTimeout()` 函数来将操作推迟到下一个 JavaScript 事件循环中。这样可以确保视图已经渲染完成,并且任何异步操作都已经完成。另外,您还可以使用 `ViewChild` 装饰器来获取元素引用,以避免在 `ngAfterViewInit()` 生命周期钩子中尝试访问元素时出现问题。

个人总结理解:

我使用ngfor来循环出 网络请求的来的数据,但是这个请求是一个 Observable,当我在ngAfterInit中去获取的时候,尚未走到下一个事件循环中(这个事件循环就是网络请求后的赋值),对于Angualr来说,视图确实是已经渲染完毕了,因为这个事件循环中,ngFor没有得到任何数据

。此时查询到dom为null,那么当下一个事件循环后,dom被渲染且赋值,并且同步到html内。这时我对ngAfterInit()中的 querySelecter()方法套上一层 setTimeOut(),是最后执行的事件循环。

衍生的问题:确定事件循环执行的顺序

在 JavaScript 中,`setTimeout()` 函数会将回调函数添加到事件队列中,并在指定的时间间隔后执行。如果您在代码中使用多个 `setTimeout()` 函数,那么这些函数的回调函数将按照它们被添加到事件队列中的顺序执行。

例如,假设您有以下代码:

console.log('Start');

setTimeout(() => {
  console.log('First');
}, 1000);

setTimeout(() => {
  console.log('Second');
}, 500);

console.log('End');

在这个例子中,`console.log()` 函数会输出 `Start` 和 `End`,然后将两个 `setTimeout()` 函数添加到事件队列中。第一个 `setTimeout()` 函数将在 1000 毫秒后执行,第二个 `setTimeout()` 函数将在 500 毫秒后执行。因此,输出顺序应该是:

Start
End
Second
First

请注意,由于 `setTimeout()` 函数的执行时间是不确定的,因此输出顺序可能会有所不同。但是,回调函数的执行顺序始终与它们被添加到事件队列中的顺序相同。

如果您需要确保一个回调函数在另一个回调函数之后执行,可以将第二个回调函数作为第一个回调函数的一部分执行。例如:

setTimeout(() => {
  console.log('First');
  setTimeout(() => {
    console.log('Second');
  }, 500);
}, 1000);

在这个例子中,第二个 `setTimeout()` 函数被添加为第一个 `setTimeout()` 函数的一部分,并在第一个回调函数执行后立即执行。这样可以确保第二个回调函数在第一个回调函数之后执行。

衍生问题的变形:angular的生命周期是一个事件循环吗?如果是,那么事件循环A中添加事件循环B,执行顺序是什么?

可以将 Angular 的生命周期钩子看作一个事件循环,因为它们定义了组件在不同时间点执行的操作。每个生命周期钩子都代表了一个特定的时间点,例如组件初始化、视图初始化、变更检测等。当 Angular 应用程序运行时,它会按照生命周期钩子定义的顺序依次执行这些操作。

在 Angular 中,每个生命周期钩子都有一个特定的目的。例如:

- `ngOnInit()` 生命周期钩子在组件初始化时执行,用于初始化组件属性和服务。
- `ngAfterViewInit()` 生命周期钩子在视图初始化完成后执行,用于访问视图元素和进行其他操作。
- `ngOnChanges()` 生命周期钩子在组件输入属性发生变化时执行,用于响应输入属性的变化。

通过按照生命周期钩子定义的顺序执行这些操作,Angular 可以确保组件和视图始终保持同步。例如,在 `ngAfterViewInit()` 生命周期钩子中访问视图元素可以确保视图已经完全初始化,并且元素已经被创建。

因此,您可以将 Angular 的生命周期钩子看作一个事件循环,其中每个生命周期钩子代表了一个特定的事件。您可以在这些事件中执行您需要的操作,并确保这些操作与组件和视图状态保持同步。

那么问题就变成了:事件循环A中添加事件循环B,执行顺序是什么

在 JavaScript 中,事件循环是通过事件队列实现的。当代码执行时,它会将回调函数添加到事件队列中,并在主线程完成当前任务后执行这些回调函数。如果在事件循环 A 中添加了一个事件循环 B,那么事件循环 B 的回调函数将被添加到事件队列 A 的末尾。

例如,假设您有以下代码:

console.log('Start');

setTimeout(() => {
  console.log('First');
}, 1000);

setTimeout(() => {
  console.log('Second');
}, 500);

setTimeout(() => {
  console.log('Third');
  setTimeout(() => {
    console.log('Fourth');
  }, 1000);
}, 1500);

console.log('End');

在这个例子中,代码会先输出 `Start` 和 `End`,然后将三个 `setTimeout()` 函数添加到事件队列中。第一个 `setTimeout()` 函数将在 1000 毫秒后执行,第二个 `setTimeout()` 函数将在 500 毫秒后执行,第三个 `setTimeout()` 函数将在 1500 毫秒后执行,并在回调函数中添加了一个新的 `setTimeout()` 函数。

因此,事件循环的执行顺序如下:

1. 输出 `Start`。
2. 输出 `End`。
3. 等待 500 毫秒,然后输出 `Second`。
4. 等待 500 毫秒,然后输出 `First`。
5. 等待 1000 毫秒,然后输出 `Third`。
6. 等待另外 1000 毫秒,然后输出 `Fourth`。

请注意,事件循环 B 的回调函数会在事件循环 A 的回调函数之后执行,因为它们被添加到了事件队列 A 的末尾。因此,在上面的示例中,`Third` 和 `Fourth` 的输出顺序取决于它们被添加到事件队列的时间。

规约这个问题:

最终结论,如果在事件循环 A 中添加了一个事件循环 B,那么事件循环 B 的回调函数将被添加到事件队列 A 的末尾。

好。ngInit()中添加了一个http异步请求,相当于在事件循环A中添加了一个事件循环B,那么这个http请求是在最后执行的,换句话说,http 在 ngAfterViewInit()之后,所以,这就是为什么ngAfterViewInit()无法选中需要的元素。

同理,对 querySelecter()添加setTimeOut之后,为什么要设置3000ms才能获取目标dom,10ms就获取不到?因为事件循环在指定的时间间隔后执行,如果http请求对完成事件晚于querySelecter,那么确实是获取不到的。

猜你喜欢

转载自blog.csdn.net/dongnihao/article/details/131797618