Skip to content

Commit c129b62

Browse files
committed
chore: update detached loader utils
1 parent 4f3d119 commit c129b62

File tree

4 files changed

+110
-3
lines changed

4 files changed

+110
-3
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, EmbeddedViewRef, Injector, NgModule, NO_ERRORS_SCHEMA, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
2+
import { ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { generateDetachedLoader, generateNativeScriptView, NgViewRef } from '@nativescript/angular';
4+
import { GridLayout, ProxyViewContainer } from '@nativescript/core';
5+
6+
@Component({
7+
template: `<ng-container #vc></ng-container><ng-template #template><GridLayout></GridLayout></ng-template>`,
8+
})
9+
export class GenerateViewComponent {
10+
@ViewChild('vc', { read: ViewContainerRef }) vc: ViewContainerRef;
11+
@ViewChild('template', { read: TemplateRef }) template: TemplateRef<void>;
12+
constructor(public injector: Injector) {}
13+
}
14+
15+
@Component({
16+
template: `<GridLayout></GridLayout>`,
17+
})
18+
export class GeneratedComponent {}
19+
20+
@NgModule({
21+
declarations: [GeneratedComponent, GenerateViewComponent],
22+
schemas: [NO_ERRORS_SCHEMA],
23+
})
24+
export class GeneratedModule {}
25+
26+
describe('generateNativeScriptView', () => {
27+
let fixture: ComponentFixture<GenerateViewComponent>;
28+
let cleanup: Array<NgViewRef<unknown> | ComponentRef<unknown> | EmbeddedViewRef<unknown>> = [];
29+
beforeEach(async () => {
30+
await TestBed.configureTestingModule({
31+
declarations: [GenerateViewComponent, GeneratedComponent],
32+
}).compileComponents();
33+
fixture = TestBed.createComponent(GenerateViewComponent);
34+
fixture.detectChanges();
35+
await fixture.whenRenderingDone();
36+
});
37+
afterEach(() => {
38+
cleanup.forEach((v) => {
39+
if (v instanceof NgViewRef) {
40+
v.ref.destroy();
41+
}
42+
if (v instanceof ComponentRef || v instanceof EmbeddedViewRef) {
43+
v.destroy();
44+
}
45+
});
46+
cleanup = [];
47+
});
48+
49+
it('should generate a native view', () => {
50+
const ngViewRef = generateNativeScriptView(GeneratedComponent, {
51+
injector: fixture.componentRef.instance.injector,
52+
});
53+
cleanup.push(ngViewRef);
54+
expect(ngViewRef.view).toBeInstanceOf(ProxyViewContainer);
55+
expect(ngViewRef.firstNativeLikeView).toBeInstanceOf(GridLayout);
56+
});
57+
58+
it('should generate a native view from template', () => {
59+
const ngViewRef = generateNativeScriptView(fixture.componentInstance.template, {
60+
injector: fixture.componentRef.instance.injector,
61+
});
62+
cleanup.push(ngViewRef);
63+
expect(ngViewRef.view).toBeInstanceOf(GridLayout);
64+
expect(ngViewRef.firstNativeLikeView).toBeInstanceOf(GridLayout);
65+
});
66+
67+
it('should reuse a DetachedLoaderRef', () => {
68+
const containerRef = generateDetachedLoader(fixture.componentRef.instance.injector.get(ComponentFactoryResolver), fixture.componentRef.instance.injector);
69+
cleanup.push(containerRef);
70+
const ngViewRef = generateNativeScriptView(GeneratedComponent, {
71+
injector: fixture.componentRef.instance.injector,
72+
detachedLoaderRef: containerRef,
73+
});
74+
cleanup.push(ngViewRef);
75+
ngViewRef.ref.destroy();
76+
expect(containerRef.hostView.destroyed).toBeFalse();
77+
});
78+
79+
it('should destroy a DetachedLoaderRef', () => {
80+
const ngViewRef = generateNativeScriptView(GeneratedComponent, {
81+
injector: fixture.componentRef.instance.injector,
82+
viewContainerRef: fixture.componentInstance.vc,
83+
});
84+
cleanup.push(ngViewRef);
85+
ngViewRef.ref.destroy();
86+
expect((ngViewRef as any).detachedLoaderRef.hostView.destroyed).toBeTrue();
87+
});
88+
89+
it('should destroy a DetachedLoaderRef from template', () => {
90+
const ngViewRef = generateNativeScriptView(fixture.componentInstance.template, {
91+
injector: fixture.componentRef.instance.injector,
92+
});
93+
cleanup.push(ngViewRef);
94+
ngViewRef.ref.destroy();
95+
expect((ngViewRef as any).detachedLoaderRef.hostView.destroyed).toBeTrue();
96+
});
97+
});

packages/angular/src/lib/cdk/portal/nsdom-portal-outlet.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export class NativeScriptDomPortalOutlet extends BasePortalOutlet {
5858
}
5959
// At this point the component has been instantiated, so we move it to the location in the DOM
6060
// where we want it to be rendered.
61+
const rootNode = this._getComponentRootNode(componentRef);
62+
if (rootNode.parent) {
63+
this._viewUtil.removeChild(rootNode.parent as View, rootNode);
64+
}
6165
this._viewUtil.appendChild(this.outletElement, this._getComponentRootNode(componentRef));
6266

6367
return componentRef;
@@ -76,7 +80,12 @@ export class NativeScriptDomPortalOutlet extends BasePortalOutlet {
7680
// But for the DomPortalOutlet the view can be added everywhere in the DOM
7781
// (e.g Overlay Container) To move the view to the specified host element. We just
7882
// re-append the existing root nodes.
79-
viewRef.rootNodes.forEach((rootNode) => this._viewUtil.appendChild(this.outletElement, rootNode));
83+
viewRef.rootNodes.forEach((rootNode) => {
84+
if (rootNode.parent) {
85+
this._viewUtil.removeChild(rootNode.parent as View, rootNode);
86+
}
87+
this._viewUtil.appendChild(this.outletElement, rootNode);
88+
});
8089

8190
// Note that we want to detect changes after the nodes have been moved so that
8291
// any directives inside the portal that are looking at the DOM inside a lifecycle

packages/angular/src/lib/detached-loader-utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function generateDetachedLoader(resolver: ComponentFactoryResolver, injec
2828
* For opening modals and others, the firstNativeLikeView should be detached.
2929
* @param typeOrTemplate ComponentType or TemplateRef that should be instanced
3030
* @param options options for creating the view
31-
* @returns ComponentRef or EmbeddedViewRef for the type
31+
* @returns NgViewRef
3232
*/
3333
export function generateNativeScriptView<T>(
3434
typeOrTemplate: Type<T> | TemplateRef<T>,
@@ -71,6 +71,7 @@ export function generateNativeScriptView<T>(
7171
});
7272
}
7373
const viewRef = new NgViewRef(componentOrTemplateRef);
74+
(viewRef as any).detachedLoaderRef = detachedLoaderRef;
7475
if (!options.keepNativeViewAttached) {
7576
viewRef.detachNativeLikeView();
7677
}

packages/angular/src/lib/view-refs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class NgViewRef<T> implements NgViewRef<T> {
3030

3131
constructor(ref: EmbeddedViewRef<T> | ComponentRef<T>) {
3232
this.ref = ref;
33-
this.view = ref instanceof EmbeddedViewRef ? ref.rootNodes.find((v) => !(v instanceof InvisibleNode)) : ref.location.nativeElement;
33+
this.view = ref instanceof ComponentRef ? ref.location.nativeElement : ref.rootNodes.find((v) => !(v instanceof InvisibleNode));
3434
this.firstNativeLikeView = getFirstNativeLikeView(this.view);
3535
}
3636

0 commit comments

Comments
 (0)