Currently in the process of developing my custom toastr service, as shown in the GIF below
https://i.sstatic.net/Zpbxs.gif
My Objective: https://stackblitz.com/edit/angular-ivy-tgm4st?file=src/app/app.component.ts But without using queryselector. It's recommended to avoid queryselector for accessing elements in the DOM within Angular.
The Problem: Whenever I click the CTA button, a toast element is added to an array of toasts that the component subscribes to and uses to update the DOM.
The toast generation looks like this:
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)"
Desired Outcome: I aim to attach an event listener to each toast element for 'animationend' to remove the HTML element. Currently, I achieve this by removing the animation classes on click with the following code snippet:
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);
});
}
Initially, I attempted subscribing to the array and using it as an event listener when new items are pushed. Then, fetching the latest toast and adding another event listener for 'animationend'. However, this method turned out to be slow and consistently returned null upon the first event triggering.
https://i.sstatic.net/YErml.gif
I've learned that utilizing querySelector in Angular is generally discouraged. So, the question remains:
How can we access dynamically generated elements in Angular without relying on querySelector?
FULL 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 to website with live code ModernnaMedia