【问题标题】:How to get a dynamically generated element in Angular without querySelector?如何在没有querySelector的情况下在Angular中获取动态生成的元素?
【发布时间】:2021-11-27 19:38:34
【问题描述】:

我目前正在创建自己的 toastr 服务,如下面的 GIF 所示



我想要实现的目标 https://stackblitz.com/edit/angular-ivy-tgm4st?file=src/app/app.component.ts 但没有查询选择器。根据我的阅读,您不应该使用 queryselector 以角度检索 DOM 中的元素


问题 每当我单击 CTA 按钮时,我都会将一个 toast 元素添加到一个 toast 数组中,该组件已订阅并用于更新 DOM。

toast 是这样生成的:

export class ToastComponent implements OnInit {
  constructor(private toast: ToastService, protected elementRef: ElementRef) {}

  toasts = this.toast.Toasts;

  <div
    class="toast-wrapper wobble-animation"
    *ngFor="let t of toasts.value"
    (click)="DestroyToast(t, $event)"

我想要什么 每当“animationend”销毁 HTML 元素时,我想在 toast 中添加一个事件监听器。我已经通过点击这行代码来做到这一点:

       DestroyToast(element, event): void {
        event.target.classList.remove('wobble-animation');
        event.target.classList.add('slide-out-animation');
        event.target.addEventListener('animationend', () => {
          this.toasts.value.splice(this.toasts.value.indexOf(element), 1);
        });
      }

我最初的想法是订阅数组并将其用作事件监听器,以便在推送某些内容时使用。然后我会使用一个函数来获取最新的 toast 并添加另一个事件监听器,即“animationend”。

我试过这样的方法:

  ngOnInit(): void {
      this.toast.Toasts.subscribe((args) => {
      this.UpdateToasts();
     });
  }
  UpdateToasts() {
    let toastElements = document.querySelectorAll('.toast');
    console.log(toastElements);
  }

但不幸的是它太慢了,并且总是在第一个事件时返回 null。


我认为我已经阅读过在 Angular 中使用 querySelector 通常是不好的做法。所以问题是:

如何在没有querySelector的情况下在Angular中获取动态生成的元素?


完整代码

Toast.Component.ts

import { ToastService } from './../../services/toast.service';
import { toast } from './toast.model';
import { Component, OnInit, ElementRef } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-toast',
  templateUrl: './toast.component.html',
  styleUrls: ['./toast.component.scss'],
})
export class ToastComponent implements OnInit {
  constructor(private toast: ToastService, protected elementRef: ElementRef) {}

  toasts = this.toast.Toasts;
  ngOnInit(): void {
    this.toast.Toasts.subscribe((args) => {
      this.UpdateToasts();
    });
  }
  ngOnDestroy() {
    this.toasts.unsubscribe();
  }
  DestroyToast(element, event): void {
    event.target.classList.remove('wobble-animation');
    event.target.classList.add('slide-out-animation');
    event.target.addEventListener('animationend', () => {
      this.toasts.value.splice(this.toasts.value.indexOf(element), 1);
    });
  }
  UpdateToasts() {
    let toastElements = document.querySelectorAll('.toast');
    console.log(toastElements);
  }
}

Toast.Component.html

<div class="toast-container">
  <div
    class="toast-wrapper wobble-animation"
    *ngFor="let t of toasts.value"
    (click)="DestroyToast(t, $event)"
  >
    <div
      class="toast default"
      [ngClass]="{ 'slide-out-animation': t.TimeLeft < 1 }"
    >
      <div class="notification-count" *ngIf="t.Count > 1">
        {{ t.Count }}
      </div>
      <div class="content-container">
        <p class="title">
          {{ t.Title }}
        </p>
        <p class="content">{{ t.Content }}</p>
      </div>
      <span class="progress">
        <span
          class="real-progress"
          [ngStyle]="{ 'width.%': t.PercentageCompleted }"
        ></span>
      </span>
    </div>
  </div>
</div>

Toast.Service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { toast } from '../components/toast/toast.model';

@Injectable({
  providedIn: 'root',
})
export class ToastService {
  public Toasts = new BehaviorSubject<Array<object>>([]);

  constructor() {}

  Toast(Title: string, Message?: string, Style?: string, Timer?: number) {
    const toastModel = new toast({
      Title: Title,
      Content: Message,
      Timer: Timer,
      Style: Style,
      TimeLeft: Timer,
      Count: 1,
      PercentageCompleted: 100,
    });
    this.AddToast(toastModel);
  }

  private AddToast(toast: toast) {
    const currentArr = this.Toasts.value;
    const updatedToast = [...currentArr, toast];
    let timer = setInterval(function () {
      toast.PercentageCompleted = toast.TimeLeft / (toast.Timer / 100);
      toast.TimeLeft = toast.TimeLeft - 10;
      if (toast.TimeLeft <= 0 || !toast.TimeLeft) {
        clearInterval(timer);
      }
    }, 10);
    this.Toasts.next(updatedToast);
  }
}

使用实时代码链接到网站ModernnaMedia

【问题讨论】:

  • 请添加一个最小代码stackblitz 示例以显示您的用例,这样每个人都可以更轻松地提供解决方案,顺便说一句。 ViewChildren 应该足以解决您的问题,您只需将您的逻辑移动到 ngOnChanges 以防止空引用
  • @LuisLimas 谢谢!我下班后会调查的。我会让你知道它是怎么回事:)
  • @LuisLimas 我已经更新并添加了 stackbiz!

标签: javascript angular typescript angular-universal


【解决方案1】:

我不能 100% 确定我理解正确,似乎有两个 animationend 事件正在发生。

我想在“动画结束”时向 toast 添加一个事件监听器以销毁 HTML 元素。

您可以直接在模板中绑定:

<div
  *ngFor="let toast of toasts"
  #toastEl
  (animationend)="DestroyToast(toastEl)"
  class="toast">
</div>
DestroyToast(toastEl: HTMLElement) {
    // …
}

【讨论】:

  • 哇不知道你能做到这一点!太好了,但不幸的是我有 2 个动画。一进一出。如果我能够使用 document.querySelector(); 我可以解决这个问题但如前所述,查询选择器返回 null。这可以通过超时解决,但我想尝试坚持角度最佳原则。但是,您的答案可能是备份解决方案。 +1
  • Angular 也有自己的动画框架,可能值得研究一下(对它的看法不同——坚持使用 CSS 动画也可以,但动画框架确实为您提供了一些集成选项)。跨度>
  • 我明白了,谢谢你的提示!我一直在研究它,但我更喜欢 CSS 动画
【解决方案2】:

就像其他人已经提到的那样,使用ViewChildren 将是“Angular”的方式,而不是查询选择器。我们还可以使用ViewChildren 订阅我们正在收听的查询列表的更改!我认为这可能适合您的代码...

首先,在 toasts 上附加一个 ref,这里我就叫它myToasts

<div
  #myToasts
  class="toast default"
  [ngClass]="{ 'slide-out-animation': t.TimeLeft < 1 }"
>

好的,现在在组件中声明查询列表:

@ViewChildren('myToasts') myToasts: QueryList<ElementRef>;

现在您可以简单地订阅 AfterViewInit 中的更改,并对元素做任何您需要做的事情:

ngAfterViewInit() {
  this.myToasts.changes.subscribe(toasts => {
    console.log('Array length: ', toasts.length);
    console.log('Array of elements: ', toasts.toArray())
  })
}

【讨论】:

  • 谢谢!这看起来很有希望。我会测试它并回复你!
【解决方案3】:

如果你在你的 observable 变量之后添加 rxjs 延迟函数,如下所示

this.toast.Toasts.pipe(delay(0)).subscribe(()=>{this.UpdateToasts();})

你不会得到空引用错误。 如果您不想使用 queryselector,您可以使用 angular viewchildren 有关更多信息,请访问 Angular 文档站点。 https://angular.io/api/core/ViewChildren

【讨论】:

  • 您也可以使用 SetTimeout,但我坚信这不是最佳实践。不过感谢您的建议
猜你喜欢
  • 2020-11-23
  • 2021-11-25
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 2020-03-30
  • 2014-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多