@mmuscat/angular-error-boundary
Version:
Error Boundaries for Angular, with a bit of Suspense.
340 lines (332 loc) • 13.2 kB
JavaScript
import { Subscriber, Subject, isObservable } from 'rxjs';
import * as i0 from '@angular/core';
import { EventEmitter, ErrorHandler, Component, SkipSelf, Output, Directive, Input, ContentChildren, Injectable, TemplateRef, NgModule } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
class ValueSubscriber extends Subscriber {
constructor(boundary, observer) {
super(observer);
this.boundary = boundary;
boundary.subscription.add(this);
}
next() {
super.complete();
this.unsubscribe();
}
error(error) {
super.error(error);
this.unsubscribe();
}
complete() {
super.complete();
this.unsubscribe();
}
unsubscribe() {
this.boundary.subscription.remove(this);
super.unsubscribe();
}
}
class CloakObserver {
constructor(boundary) {
this.boundary = boundary;
}
next(source) {
const subscriber = new ValueSubscriber(this.boundary, this);
subscriber.add(source.subscribe(subscriber));
}
error(error) {
this.boundary.handleError(error);
}
complete() {
const { boundary } = this;
if (boundary.refCount > 0) {
boundary.refCount--;
if (boundary.refCount === 0) {
boundary.cloak(false);
}
}
}
}
class NgCloak {
constructor(elementRef, errorHandler, changeDetectorRef) {
this.elementRef = elementRef;
this.errorHandler = errorHandler;
this.changeDetectorRef = changeDetectorRef;
this.cloaked = false;
this.refCount = 0;
this.observer = new CloakObserver(this);
this.queue = new Subject();
this.subscription = this.subscribe();
this.cloakChange = new EventEmitter();
}
get element() {
return this.elementRef.nativeElement;
}
register(parent) {
this.parent = parent;
}
handleError(value) {
if (isObservable(value)) {
this.refCount++;
this.queue.next(value);
this.cloak(true);
}
else {
this.refCount = 0;
this.subscription.unsubscribe();
this.subscription = this.subscribe();
this.cloak(false);
this.errorHandler.handleError(value);
}
}
cloak(cloaked) {
this.cloaked = cloaked;
if (cloaked)
this.changeDetectorRef.detach();
else
this.changeDetectorRef.reattach();
if (!this.parent)
this.render();
}
render() {
this.changeDetectorRef.detectChanges();
}
subscribe() {
return this.queue.subscribe(this.observer);
}
ngAfterContentInit() {
this.cloak(this.refCount > 0);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
NgCloak.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: NgCloak, deps: [{ token: i0.ElementRef }, { token: i0.ErrorHandler, skipSelf: true }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
NgCloak.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.0", type: NgCloak, selector: "ng-cloak", outputs: { cloakChange: "cloakChange" }, providers: [
{
provide: ErrorHandler,
useExisting: NgCloak,
},
], ngImport: i0, template: `
<ng-content
select="fallback, [fallback]"
*ngIf="cloaked; else content"
></ng-content>
<ng-template #content>
<ng-content></ng-content>
</ng-template>
`, isInline: true, directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: NgCloak, decorators: [{
type: Component,
args: [{
selector: "ng-cloak",
template: `
<ng-content
select="fallback, [fallback]"
*ngIf="cloaked; else content"
></ng-content>
<ng-template #content>
<ng-content></ng-content>
</ng-template>
`,
providers: [
{
provide: ErrorHandler,
useExisting: NgCloak,
},
],
}]
}], ctorParameters: function () {
return [{ type: i0.ElementRef }, { type: i0.ErrorHandler, decorators: [{
type: SkipSelf
}] }, { type: i0.ChangeDetectorRef }];
}, propDecorators: { cloakChange: [{
type: Output
}] } });
class CloakListObserver {
constructor(list, child, children) {
this.list = list;
this.child = child;
this.children = children;
}
next() {
this.list.render(this.children);
}
}
class NgCloakList {
constructor(elementRef, renderer) {
this.elementRef = elementRef;
this.renderer = renderer;
this.revealOrder = "together";
}
render(children) {
const { elementRef: { nativeElement }, renderer, revealOrder, tail, } = this;
let child;
let previous = null;
let renderChildren = children.slice();
if (revealOrder === "reverse") {
renderChildren = renderChildren.reverse();
}
while ((child = renderChildren.shift())) {
if (tail === "hidden" && child.cloaked)
break;
if (revealOrder === "reverse") {
renderer.insertBefore(nativeElement, child, previous);
}
else {
renderer.appendChild(nativeElement, child.element);
}
previous = child;
child.render();
if (tail === "collapsed" && child.cloaked)
break;
}
while ((child = renderChildren.shift())) {
renderer.removeChild(nativeElement, child.element);
}
}
subscribe(children) {
for (const child of children) {
child.cloakChange.subscribe(new CloakListObserver(this, child, children));
}
}
ngAfterContentInit() {
if (this.children) {
this.subscribe(this.children.toArray());
}
}
}
NgCloakList.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: NgCloakList, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
NgCloakList.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.0.0", type: NgCloakList, selector: "cloak-list", inputs: { revealOrder: "revealOrder", tail: "tail" }, queries: [{ propertyName: "children", predicate: NgCloak, descendants: true }], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: NgCloakList, decorators: [{
type: Directive,
args: [{
selector: "cloak-list",
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { revealOrder: [{
type: Input
}], tail: [{
type: Input
}], children: [{
type: ContentChildren,
args: [NgCloak, { descendants: true }]
}] } });
class ErrorLogger {
error(error) {
console.error(error);
}
}
ErrorLogger.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: ErrorLogger, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
ErrorLogger.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: ErrorLogger, providedIn: "root" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: ErrorLogger, decorators: [{
type: Injectable,
args: [{ providedIn: "root" }]
}] });
class ErrorBoundary {
constructor(changeDetectorRef, logger, errorHandler) {
this.changeDetectorRef = changeDetectorRef;
this.logger = logger;
this.errorHandler = errorHandler;
this.hasError = false;
this.error = new EventEmitter();
}
ngDoCheck() {
if (this.hasError)
return;
try {
this.changeDetectorRef.detectChanges();
}
catch (error) {
this.handleError(error);
}
}
ngAfterViewInit() {
this.changeDetectorRef.detach();
}
handleError(fault) {
try {
this.hasError = true;
this.changeDetectorRef.detectChanges();
this.logger.error(fault);
this.error.emit(new ErrorEvent("ErrorBoundary", {
error: fault,
}));
}
catch (doubleFault) {
this.errorHandler.handleError(fault);
if (fault !== doubleFault) {
this.errorHandler.handleError(doubleFault);
}
}
}
retry() {
this.hasError = false;
this.changeDetectorRef.detectChanges();
}
}
ErrorBoundary.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: ErrorBoundary, deps: [{ token: i0.ChangeDetectorRef }, { token: ErrorLogger }, { token: i0.ErrorHandler, skipSelf: true }], target: i0.ɵɵFactoryTarget.Component });
ErrorBoundary.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.0", type: ErrorBoundary, selector: "error-boundary", outputs: { error: "error" }, providers: [
{
provide: ErrorHandler,
useExisting: ErrorBoundary,
},
], queries: [{ propertyName: "template", predicate: TemplateRef }], ngImport: i0, template: `
<ng-container *ngIf="hasError; else template?.first ?? null">
<ng-content select="fallback, [fallback]"></ng-content>
</ng-container>
`, isInline: true, directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: ErrorBoundary, decorators: [{
type: Component,
args: [{
selector: "error-boundary",
template: `
<ng-container *ngIf="hasError; else template?.first ?? null">
<ng-content select="fallback, [fallback]"></ng-content>
</ng-container>
`,
providers: [
{
provide: ErrorHandler,
useExisting: ErrorBoundary,
},
],
}]
}], ctorParameters: function () {
return [{ type: i0.ChangeDetectorRef }, { type: ErrorLogger }, { type: i0.ErrorHandler, decorators: [{
type: SkipSelf
}] }];
}, propDecorators: { error: [{
type: Output
}], template: [{
type: ContentChildren,
args: [TemplateRef, { descendants: false }]
}] } });
class Fallback {
}
Fallback.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: Fallback, deps: [], target: i0.ɵɵFactoryTarget.Directive });
Fallback.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.0.0", type: Fallback, selector: "fallback, [fallback]", ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: Fallback, decorators: [{
type: Directive,
args: [{ selector: "fallback, [fallback]" }]
}] });
class BoundaryModule {
}
BoundaryModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: BoundaryModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
BoundaryModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: BoundaryModule, declarations: [ErrorBoundary, NgCloakList, NgCloak, Fallback], imports: [CommonModule], exports: [ErrorBoundary, NgCloakList, NgCloak, Fallback] });
BoundaryModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: BoundaryModule, imports: [[CommonModule]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: BoundaryModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule],
declarations: [ErrorBoundary, NgCloakList, NgCloak, Fallback],
exports: [ErrorBoundary, NgCloakList, NgCloak, Fallback],
}]
}] });
/*
* Public API Surface of boundary
*/
/**
* Generated bundle index. Do not edit.
*/
export { BoundaryModule, ErrorBoundary, ErrorLogger, Fallback, NgCloak, NgCloakList };
//# sourceMappingURL=mmuscat-angular-error-boundary.mjs.map