Skip to content

Commit a85b1ee

Browse files
author
Alexander Vakrilov
committed
Merge pull request #274 from NativeScript/detached-loader-change-detection
Detached loader change detection
2 parents 814a8b4 + ba324cd commit a85b1ee

File tree

2 files changed

+132
-4
lines changed

2 files changed

+132
-4
lines changed

nativescript-angular/common/detached-loader.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import {ComponentRef, ViewContainerRef, Component, Type, ViewChild, ComponentResolver} from '@angular/core';
1+
import {ComponentRef, ViewContainerRef, Component, Type, ViewChild, ComponentResolver, ChangeDetectorRef, Host} from '@angular/core';
2+
import trace = require("trace");
23

34
type AnyComponentRef = ComponentRef<any>;
45
interface PendingLoadEntry {
56
componentType: Type;
67
resolveCallback: (AnyComponentRef) => void;
78
}
89

10+
export const CATEGORY = "detached-loader";
11+
function log(message: string) {
12+
trace.write(message, CATEGORY);
13+
}
14+
15+
916
/**
1017
* Wrapper component used for loading components when navigating
1118
* It uses DetachedContainer as selector so that it is containerRef is not attached to the visual tree.
@@ -20,10 +27,11 @@ export class DetachedLoader {
2027
private viewLoaded = false;
2128
private pendingLoads: PendingLoadEntry[] = [];
2229

23-
constructor(private compiler: ComponentResolver) {
24-
}
30+
constructor(private compiler: ComponentResolver, private changeDetector: ChangeDetectorRef) { }
2531

2632
public ngAfterViewInit() {
33+
log("DetachedLoader.ngAfterViewInit");
34+
2735
this.viewLoaded = true;
2836
this.pendingLoads.forEach(loadEntry => {
2937
this.loadInLocation(loadEntry.componentType).then(loadedRef => {
@@ -35,15 +43,30 @@ export class DetachedLoader {
3543
private loadInLocation(componentType: Type): Promise<ComponentRef<any>> {
3644
return this.compiler.resolveComponent(componentType).then((componentFactory) => {
3745
return this.containerRef.createComponent(componentFactory, this.containerRef.length, this.containerRef.parentInjector, null);
38-
});
46+
}).then((compRef) => {
47+
log("DetachedLoader.loadInLocation component loaded -> markForCheck");
48+
// Component is created, buit may not be checked if we are loading
49+
// inside component with OnPush CD strategy. Mark us for check to be sure CD will reach us.
50+
// We are inside a promise here so no need for setTimeout - CD should trigger after the promise.
51+
this.changeDetector.markForCheck();
52+
return compRef;
53+
})
3954
}
4055

4156
public loadComponent(componentType: Type): Promise<ComponentRef<any>> {
57+
log("DetachedLoader.loadComponent viewLoaded: " + this.viewLoaded);
58+
4259
// Check if called before placeholder is initialized.
4360
// Delay load if so.
4461
if (this.viewLoaded) {
4562
return this.loadInLocation(componentType);
4663
} else {
64+
// loadComponent called, but detached-loader is still not initialized.
65+
// Mark it for change and trigger change detection to be sure it will be initialized,
66+
// so that loading can conitionue.
67+
log("DetachedLoader.loadComponent -> markForCheck(with setTimeout())")
68+
setTimeout(() => this.changeDetector.markForCheck(), 0);
69+
4770
return new Promise((resolve, reject) => {
4871
this.pendingLoads.push({
4972
componentType: componentType,
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//make sure you import mocha-config before @angular/core
2+
import {assert} from "./test-config";
3+
import {Component, ElementRef, Renderer, AfterViewInit, OnInit, ViewChild, ChangeDetectionStrategy} from "@angular/core";
4+
import {ProxyViewContainer} from "ui/proxy-view-container";
5+
import {Red} from "color/known-colors";
6+
import {dumpView} from "./test-utils";
7+
import {TestApp} from "./test-app";
8+
import {LayoutBase} from "ui/layouts/layout-base";
9+
import {StackLayout} from "ui/layouts/stack-layout";
10+
import {DetachedLoader} from "nativescript-angular/common/detached-loader";
11+
12+
@Component({
13+
template: `<StackLayout><Label text="COMPONENT"></Label></StackLayout>`
14+
})
15+
class TestComponent { }
16+
17+
18+
class LoaderComponentBase {
19+
@ViewChild(DetachedLoader) public loader: DetachedLoader;
20+
21+
public ready: Promise<LoaderComponent>;
22+
private resolve;
23+
constructor() {
24+
this.ready = new Promise((reslove, reject) => {
25+
this.resolve = reslove;
26+
})
27+
}
28+
ngAfterViewInit() {
29+
this.resolve(this);
30+
}
31+
}
32+
33+
@Component({
34+
selector: 'loader-component-on-push',
35+
directives: [DetachedLoader],
36+
template: `
37+
<StackLayout>
38+
<DetachedContainer #loader></DetachedContainer>
39+
</StackLayout>
40+
`
41+
})
42+
export class LoaderComponent extends LoaderComponentBase { }
43+
44+
@Component({
45+
selector: 'loader-component-on-push',
46+
directives: [DetachedLoader],
47+
changeDetection: ChangeDetectionStrategy.OnPush,
48+
template: `
49+
<StackLayout>
50+
<DetachedContainer #loader></DetachedContainer>
51+
</StackLayout>
52+
`
53+
})
54+
export class LoaderComponentOnPush extends LoaderComponentBase { }
55+
56+
describe ('DetachedLoader', () => {
57+
let testApp: TestApp = null;
58+
59+
before(() => {
60+
return TestApp.create().then((app) => {
61+
testApp = app;
62+
})
63+
});
64+
65+
after(() => {
66+
testApp.dispose();
67+
});
68+
69+
afterEach(() => {
70+
testApp.disposeComponents();
71+
});
72+
73+
it("creates component", (done) => {
74+
testApp.loadComponent(LoaderComponent)
75+
.then((componentRef) => {
76+
// wait for the ngAfterViewInit
77+
return (<LoaderComponent>componentRef.instance).ready;
78+
})
79+
.then((comp) => {
80+
// load test component with loader
81+
return comp.loader.loadComponent(TestComponent);
82+
})
83+
.then((compRef) => {
84+
done();
85+
})
86+
.catch(done);
87+
});
88+
89+
90+
it("creates component when ChangeDetectionStrategy is OnPush", (done) => {
91+
testApp.loadComponent(LoaderComponentOnPush)
92+
.then((componentRef) => {
93+
// wait for the ngAfterViewInit
94+
return (<LoaderComponentOnPush>componentRef.instance).ready;
95+
})
96+
.then((comp) => {
97+
// load test component with loader
98+
return comp.loader.loadComponent(TestComponent);
99+
})
100+
.then((compRef) => {
101+
done();
102+
})
103+
.catch(done);
104+
});
105+
})

0 commit comments

Comments
 (0)