projects/ng-snotify/src/lib/components/toast/toast.component.ts
OnInit
OnDestroy
AfterContentInit
encapsulation | ViewEncapsulation.None |
selector | ng-snotify-toast |
templateUrl | ./toast.component.html |
Properties |
Methods |
Inputs |
Outputs |
constructor(service: SnotifyService)
|
||||||
Parameters :
|
toast | |
Type : SnotifyToast
|
|
Get toast from notifications array |
stateChanged | |
Type : EventEmitter
|
|
initToast |
initToast()
|
Initialize base toast config
Returns :
void
|
ngAfterContentInit |
ngAfterContentInit()
|
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Unsubscribe subscriptions
Returns :
void
|
ngOnInit |
ngOnInit()
|
Init base options. Subscribe to toast changed, toast deleted
Returns :
void
|
onClick |
onClick()
|
Trigger OnClick lifecycle
Returns :
void
|
onExitTransitionEnd |
onExitTransitionEnd()
|
Remove toast completely after animation
Returns :
void
|
onMouseEnter |
onMouseEnter()
|
Trigger onHoverEnter lifecycle
Returns :
void
|
onMouseLeave |
onMouseLeave()
|
Trigger onHoverLeave lifecycle
Returns :
void
|
onRemove |
onRemove()
|
Trigger beforeDestroy lifecycle. Removes toast
Returns :
void
|
startTimeout | ||||||||||
startTimeout(startTime: number)
|
||||||||||
Start progress bar
Parameters :
Returns :
void
|
animationFrame |
Type : number
|
requestAnimationFrame id |
state |
Type : object
|
Default value : {
paused: false,
progress: 0,
animation: '',
isDestroying: false,
promptType: SnotifyStyle.prompt
}
|
Toast state |
toastChangedSubscription |
Type : Subscription
|
toastDeletedSubscription |
Type : Subscription
|
import {
AfterContentInit,
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewEncapsulation
} from '@angular/core';
import { SnotifyService } from '../../services/snotify.service';
import { SnotifyToast } from '../../models/snotify-toast.model';
import { Subscription } from 'rxjs';
import { SnotifyEvent } from '../../types/event.type';
import { SnotifyStyle } from '../../enums/snotify-style.enum';
@Component({
selector: 'ng-snotify-toast',
templateUrl: './toast.component.html',
encapsulation: ViewEncapsulation.None
})
export class ToastComponent implements OnInit, OnDestroy, AfterContentInit {
/**
* Get toast from notifications array
*/
@Input() toast: SnotifyToast;
@Output() stateChanged = new EventEmitter<SnotifyEvent>();
toastDeletedSubscription: Subscription;
toastChangedSubscription: Subscription;
/**
* requestAnimationFrame id
*/
animationFrame: number;
/**
* Toast state
*/
state = {
paused: false,
progress: 0,
animation: '',
isDestroying: false,
promptType: SnotifyStyle.prompt
};
constructor(private service: SnotifyService) {}
// Lifecycles
/**
* Init base options. Subscribe to toast changed, toast deleted
*/
ngOnInit() {
this.toastChangedSubscription = this.service.toastChanged.subscribe((toast: SnotifyToast) => {
if (this.toast.id === toast.id) {
this.initToast();
}
});
this.toastDeletedSubscription = this.service.toastDeleted.subscribe(id => {
if (this.toast.id === id) {
this.onRemove();
}
});
if (!this.toast.config.timeout) {
this.toast.config.showProgressBar = false;
}
this.toast.eventEmitter.next('mounted');
this.state.animation = 'snotifyToast--in';
}
ngAfterContentInit() {
setTimeout(() => {
this.stateChanged.emit('beforeShow');
this.toast.eventEmitter.next('beforeShow');
this.state.animation = this.toast.config.animation.enter;
}, this.service.config.toast.animation.time / 5); // time to show toast push animation (snotifyToast--in)
}
/**
* Unsubscribe subscriptions
*/
ngOnDestroy(): void {
cancelAnimationFrame(this.animationFrame);
this.toast.eventEmitter.next('destroyed');
this.toastChangedSubscription.unsubscribe();
this.toastDeletedSubscription.unsubscribe();
}
/*
Event hooks
*/
/**
* Trigger OnClick lifecycle
*/
onClick() {
this.toast.eventEmitter.next('click');
if (this.toast.config.closeOnClick) {
this.service.remove(this.toast.id);
}
}
/**
* Trigger beforeDestroy lifecycle. Removes toast
*/
onRemove() {
this.state.isDestroying = true;
this.toast.eventEmitter.next('beforeHide');
this.stateChanged.emit('beforeHide');
this.state.animation = this.toast.config.animation.exit;
setTimeout(() => {
this.stateChanged.emit('hidden');
this.state.animation = 'snotifyToast--out';
this.toast.eventEmitter.next('hidden');
setTimeout(() => this.service.remove(this.toast.id, true), this.toast.config.animation.time / 2);
}, this.toast.config.animation.time / 2);
}
/**
* Trigger onHoverEnter lifecycle
*/
onMouseEnter() {
this.toast.eventEmitter.next('mouseenter');
if (this.toast.config.pauseOnHover) {
this.state.paused = true;
}
}
/**
* Trigger onHoverLeave lifecycle
*/
onMouseLeave() {
if (this.toast.config.pauseOnHover && this.toast.config.timeout) {
this.state.paused = false;
this.startTimeout(this.toast.config.timeout * this.state.progress);
}
this.toast.eventEmitter.next('mouseleave');
}
/**
* Remove toast completely after animation
*/
onExitTransitionEnd() {
if (this.state.isDestroying) {
return;
}
this.initToast();
this.toast.eventEmitter.next('shown');
}
/*
Common
*/
/**
* Initialize base toast config
*
*/
initToast(): void {
if (this.toast.config.timeout > 0) {
this.startTimeout(0);
}
}
/**
* Start progress bar
* @param startTime number
*/
startTimeout(startTime: number = 0) {
const start = performance.now();
const calculate = () => {
this.animationFrame = requestAnimationFrame(timestamp => {
const runtime = timestamp + startTime - start;
const progress = Math.min(runtime / this.toast.config.timeout, 1);
if (this.state.paused) {
cancelAnimationFrame(this.animationFrame);
} else if (runtime < this.toast.config.timeout) {
this.state.progress = progress;
calculate();
} else {
this.state.progress = 1;
cancelAnimationFrame(this.animationFrame);
this.service.remove(this.toast.id);
}
});
};
calculate();
}
}
<div
[attr.role]="toast.config.type === state.promptType ? 'dialog' : 'alert'"
[attr.aria-labelledby]="'snotify_' + toast.id"
[attr.aria-modal]="toast.config.type === state.promptType"
[ngClass]="[
'snotifyToast animated',
'snotify-' + toast.config.type,
state.animation,
toast.valid === undefined ? '' : toast.valid ? 'snotifyToast--valid' : 'snotifyToast--invalid'
]"
[ngStyle]="{
'-webkit-transition': toast.config.animation.time + 'ms',
transition: toast.config.animation.time + 'ms',
'-webkit-animation-duration': toast.config.animation.time + 'ms',
'animation-duration': toast.config.animation.time + 'ms'
}"
(animationend)="onExitTransitionEnd()"
(click)="onClick()"
(mouseenter)="onMouseEnter()"
(mouseleave)="onMouseLeave()"
>
<div class="snotifyToast__progressBar" *ngIf="toast.config.showProgressBar">
<span class="snotifyToast__progressBar__percentage" [ngStyle]="{ width: state.progress * 100 + '%' }"></span>
</div>
<div class="snotifyToast__inner" *ngIf="!toast.config.html; else toastHTML">
<div class="snotifyToast__title" [attr.id]="'snotify_' + toast.id" *ngIf="toast.title">
{{ toast.title | truncate: toast.config.titleMaxLength }}
</div>
<div class="snotifyToast__body" *ngIf="toast.body">{{ toast.body | truncate: toast.config.bodyMaxLength }}</div>
<ng-snotify-prompt *ngIf="toast.config.type === state.promptType" [toast]="toast"> </ng-snotify-prompt>
<div
*ngIf="!toast.config.icon; else elseBlock"
[ngClass]="['snotify-icon', toast.config.iconClass || 'snotify-icon--' + toast.config.type]"
></div>
<ng-template #elseBlock>
<img class="snotify-icon" [src]="toast.config.icon" />
</ng-template>
</div>
<ng-template #toastHTML>
<div class="snotifyToast__inner" [innerHTML]="toast.config.html"></div>
</ng-template>
<ng-snotify-button *ngIf="toast.config.buttons" [toast]="toast"></ng-snotify-button>
</div>