Website-Suche

javascript - Wie erhält man ein dynamisch generiertes Element in Angular ohne querySelector?


Ich erstelle derzeit meinen eigenen Toastr-Dienst, wie im GIF unten zu sehen ist



Was ich erreichen möchte https://stackblitz.com/edit/angular-ivy-tgm4st?file=src/app/app.component.ts Aber ohne queryselector. Aus dem, was ich gelesen habe, sollte man in Angular keinen queryselector verwenden, um Elemente im DOM abzurufen


Das Problem Jedes Mal, wenn ich auf die CTA-Schaltfläche klicke, füge ich ein Toast-Element zu einem Array von Toasts hinzu, auf das die Komponente abonniert ist und das sie nutzt, um das DOM zu aktualisieren.

Die Toasts werden folgendermaßen erzeugt:

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)"

Was ich will Ich möchte einen Eventlistener zum Toast hinzufügen, wann immer 'animationend' auftritt, um das HTML-Element zu zerstören. Ich mache das bereits durch Klicken mit dieser Codezeile:

       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);
        });
      }

Mein erster Gedanke war, das Array zu abonnieren und dies als Eventlistener zu verwenden, wenn etwas hinzugefügt wird. Dann würde ich eine Funktion verwenden, um den neuesten Toast abzurufen und einen weiteren Eventlistener hinzuzufügen, den 'animationend'-Listener.

Ich habe die Methode so ausprobiert:

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

Aber leider ist es zu langsam und gibt beim ersten Ereignis immer null zurück.


Ich glaube, dass ich gelesen habe, dass die Verwendung von querySelector in Angular im Allgemeinen eine schlechte Praxis ist. Also lautet die Frage:

Wie erhält man ein dynamisch erzeugtes Element in Angular ohne querySelector?


VOLLSTÄNDIGER CODE

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);
  }
}

Link zur Website mit Live-Code ModernnaMedia

Antwort #1

Ich bin mir nicht 100% sicher, ob ich dich richtig verstanden habe, es scheinen zwei animationend-Ereignisse stattzufinden.

Ich möchte einen Eventlistener zum Toast hinzufügen, wann immer 'animationend' auftritt, um das HTML-Element zu zerstören.

Sie können das direkt in der Vorlage binden:

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

Antwort #2

Wie bereits von anderen erwähnt, wäre die Verwendung von ViewChildren der "Angular"-Weg, dies zu tun, anstatt queryselector. Mit ViewChildren können wir auch auf Änderungen der QueryList, der wir zuhören, reagieren! Ich denke, das ist wahrscheinlich für deinen Code geeignet...

Also zuerst, hänge eine Referenz an die Toasts an, hier nenne ich sie einfach myToasts:

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

OK, deklariere nun die Abfrageliste in der Komponente:

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

Jetzt können Sie einfach die Änderungen in AfterViewInit abonnieren und alles tun, was Sie mit den Elementen tun müssen:

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

Verwandte Artikel