-
-
Notifications
You must be signed in to change notification settings - Fork 241
/
Copy pathdetached-loader.ts
83 lines (70 loc) · 3.41 KB
/
detached-loader.ts
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
import {ComponentRef, ComponentFactory, ViewContainerRef, Component,
Type, ViewChild, ComponentResolver, ChangeDetectorRef, Host} from '@angular/core';
import trace = require("trace");
type AnyComponentRef = ComponentRef<any>;
interface PendingLoadEntry {
componentType: Type;
resolveCallback: (AnyComponentRef) => void;
}
export const CATEGORY = "detached-loader";
function log(message: string) {
trace.write(message, CATEGORY);
}
/**
* Wrapper component used for loading components when navigating
* It uses DetachedContainer as selector so that it is containerRef is not attached to the visual tree.
*/
@Component({
selector: 'DetachedContainer',
template: `<Placeholder #loader></Placeholder>`
})
export class DetachedLoader {
@ViewChild('loader', { read: ViewContainerRef }) childContainerRef: ViewContainerRef;
private viewLoaded = false;
private pendingLoads: PendingLoadEntry[] = [];
constructor(private compiler: ComponentResolver, private changeDetector: ChangeDetectorRef, private containerRef: ViewContainerRef) { }
public ngAfterViewInit() {
log("DetachedLoader.ngAfterViewInit");
this.viewLoaded = true;
this.pendingLoads.forEach(loadEntry => {
this.loadInLocation(loadEntry.componentType).then(loadedRef => {
loadEntry.resolveCallback(loadedRef);
});
});
}
private loadInLocation(componentType: Type): Promise<ComponentRef<any>> {
return this.compiler.resolveComponent(componentType).then((componentFactory) => {
return this.childContainerRef.createComponent(componentFactory, this.childContainerRef.length, this.childContainerRef.parentInjector, null);
}).then((compRef) => {
log("DetachedLoader.loadInLocation component loaded -> markForCheck");
// Component is created, buit may not be checked if we are loading
// inside component with OnPush CD strategy. Mark us for check to be sure CD will reach us.
// We are inside a promise here so no need for setTimeout - CD should trigger after the promise.
this.changeDetector.markForCheck();
return compRef;
})
}
public loadComponent(componentType: Type): Promise<ComponentRef<any>> {
log("DetachedLoader.loadComponent viewLoaded: " + this.viewLoaded);
// Check if called before placeholder is initialized.
// Delay load if so.
if (this.viewLoaded) {
return this.loadInLocation(componentType);
} else {
// loadComponent called, but detached-loader is still not initialized.
// Mark it for change and trigger change detection to be sure it will be initialized,
// so that loading can conitionue.
log("DetachedLoader.loadComponent -> markForCheck(with setTimeout())")
setTimeout(() => this.changeDetector.markForCheck(), 0);
return new Promise((resolve, reject) => {
this.pendingLoads.push({
componentType: componentType,
resolveCallback: resolve
});
});
}
}
public loadWithFactory<T>(factory: ComponentFactory<T>): ComponentRef<T> {
return this.containerRef.createComponent(factory, this.containerRef.length, this.containerRef.parentInjector, null);
}
}