For this specific task, I developed a custom overlay component instead of using Bootstrap modal due to the need for distinct window modals and progress modals on components.
Overlay
@Component({
selector: 'overlay',
template:
`<div [ngClass]="isOpen ? 'opened' : 'closed'">
<div class="modal" role="dialog">
<div class="modalBody">
<div *ngIf="isSaving">
<span class="text-success text-bold">
Saving...
</span>
</div>
<div *ngIf="isSaved">
<span class="text-success text-bold">
Saved.
</span>
</div>
</div>
</div>
</div>`,
styles: [
'.modal { position:absolute; width: 100%; height: 100%; margin: -30px; background-color: rgba(255, 255, 255, 0.7); z-index: 1000; text-align: center; }',
'.closed { visibility: hidden; }',
'.opened { visibility: visible; }',
'.modalBody { top: 45%; left: 25%; width: 50%; position: absolute; }',
'.text-bold { font-weight: 800; font-size: 1.5em; }'
]
})
export class Overlay implements OnChanges, AfterContentInit {
@Input() isSaving: boolean = false;
@Input() isSaved: boolean = false;
@Input() containerElement: HTMLElement;
isOpen = false;
private modalElement;
constructor(private element: ElementRef, private animationBuilder: AnimationBuilder) { }
ngOnChanges() {
if (this.modalElement) {
if (this.isSaving == true || this.isSaved == true) {
this.toggleAnimation(true);
}
else if (this.isSaving == false && this.isSaved == false) {
this.toggleAnimation(false);
}
}
}
ngAfterContentInit() {
this.containerElement.style.position = 'relative';
this.modalElement = this.element.nativeElement.querySelector('.modal');
}
private toggleAnimation(isOpen) {
var startCss = { backgroundColor: 'rgba(255, 255, 255, 0)' };
var endCss = { backgroundColor: 'rgba(255, 255, 255, 0.7)' };
if (isOpen) {
this.isOpen = true
this.animation(
true,
this.modalElement,
400,
startCss,
endCss,
null
);
}
else {
this.animation(
isOpen,
this.modalElement,
400,
startCss,
endCss,
() => {
this.isOpen = false;
}
);
}
}
private animation(isStart, element, duration, startCss, endCss, finishedCallback) {
var animation = this.animationBuilder.css();
animation.setDuration(duration);
if (isStart) {
animation.setFromStyles(startCss).setToStyles(endCss);
} else {
animation.setFromStyles(endCss).setToStyles(startCss)
}
animation.start(element);
if (finishedCallback) {
setTimeout(finishedCallback, duration);
}
}
}
Usage
In its current state, the overlay
component requires a relative container for proper display. The CSS may need adjustments for mobile devices and non-positioned containers. Here's how it's currently implemented:
HTML
<form action="/edit" method="post" #myForm="ngForm" (ngSubmit)="save ajax method that will update the isSaving and isSaved accordingly" novalidate>
<div style="position: relative;" #overlayContainer>
<overlay [isSaving]="isSaving" [isSaved]="isSaved" [containerElement]="overlayContainer"></overlay>
</div>
</form>
After submitting the form
, the overlay
appears within the specified containerElement
for 400ms, then fades out and hides until the next save operation. Handling the isSaving
and isSaved
values is the responsibility of the parent component utilizing the overlay
.