-
-
Notifications
You must be signed in to change notification settings - Fork 75
Expand file tree
/
Copy pathparcel.component.ts
More file actions
139 lines (119 loc) · 4 KB
/
parcel.component.ts
File metadata and controls
139 lines (119 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import {
Component,
ElementRef,
ChangeDetectionStrategy,
input,
effect,
untracked,
afterNextRender,
DestroyRef,
inject,
} from '@angular/core';
import { Parcel, ParcelConfig, AppProps } from 'single-spa';
const enum Action {
Mount = 'mount',
Update = 'update',
Unmount = 'unmount',
}
// This will be provided through Terser global definitions by Angular CLI. This will
// help to tree-shake away the code unneeded for production bundles.
declare const ngDevMode: boolean;
@Component({
selector: 'parcel',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
})
export class ParcelComponent {
readonly config = input<ParcelConfig | null>(null);
readonly mountParcel = input<AppProps['mountParcel'] | null>(null);
readonly onParcelMount = input<(() => void) | null>(null);
readonly wrapWith = input('div');
readonly customProps = input<Record<string, unknown>>({});
readonly appendTo = input<Node | null>(null);
readonly handleError = input((error: Error) => console.error(error));
private hasError = false;
private unmounted?: boolean;
private wrapper: HTMLElement | null = null;
private parcel: Parcel | null = null;
private task: Promise<void> | null = null;
constructor(private host: ElementRef<HTMLElement>) {
effect(() => {
const customProps = this.customProps();
untracked(() => {
this.scheduleTask(Action.Update, () => {
this.parcel?.update?.(customProps);
});
});
});
afterNextRender(() => {
this.scheduleTask(Action.Mount, () => {
if ((typeof ngDevMode === 'undefined' || ngDevMode) && this.mountParcel === null) {
throw new Error(
'single-spa-angular: the [mountParcel] binding is required when using the <parcel> component. You can either (1) import mountRootParcel from single-spa or (2) use the mountParcel prop provided to single-spa applications.',
);
}
this.wrapper = document.createElement(this.wrapWith());
const appendTo = this.appendTo();
if (appendTo !== null) {
appendTo.appendChild(this.wrapper);
} else {
this.host.nativeElement.appendChild(this.wrapper);
}
const mountParcel = this.mountParcel();
this.parcel = mountParcel!(this.config()!, {
...this.customProps(),
domElement: this.wrapper,
});
const onParcelMount = this.onParcelMount();
if (onParcelMount !== null) {
this.parcel.mountPromise.then(onParcelMount);
}
this.unmounted = false;
return this.parcel.mountPromise;
});
});
inject(DestroyRef).onDestroy(() => {
this.scheduleTask(Action.Unmount, () => {
if (this.parcel?.getStatus() === 'MOUNTED') {
return this.parcel.unmount();
}
});
if (this.wrapper !== null) {
this.wrapper.parentNode!.removeChild(this.wrapper);
}
this.unmounted = true;
});
}
private scheduleTask(action: Action, task: () => void | Promise<any>): void {
if (this.hasError && action !== Action.Unmount) {
// In an error state, we don't do anything anymore except for unmounting
return;
}
this.task = (this.task || Promise.resolve())
.then(() => {
if (this.unmounted && action !== Action.Unmount) {
// Never do anything once the angular component unmounts
return;
}
return task();
})
.catch((error: Error) => {
this.task = Promise.resolve();
this.hasError = true;
if (error?.message) {
error.message = `During '${action}', parcel threw an error: ${error.message}`;
}
const handleError = this.handleError();
if (typeof handleError === 'function') {
handleError(error);
} else {
setTimeout(() => {
throw error;
});
}
// No more things to do should be done -- the parcel is in an error state
throw error;
});
}
}