fix(ivy): allow root components to inject ViewContainerRef (#26682)

PR Close #26682
This commit is contained in:
Pawel Kozlowski
2018-10-17 11:01:35 +02:00
committed by Matias Niemelä
parent 578e4c7642
commit ede65dbede
12 changed files with 175 additions and 14 deletions

View File

@ -872,6 +872,9 @@
{
"name": "isProceduralRenderer"
},
{
"name": "isRootView"
},
{
"name": "isSanitizable"
},
@ -920,6 +923,12 @@
{
"name": "nativeInsertBefore"
},
{
"name": "nativeNextSibling"
},
{
"name": "nativeParentNode"
},
{
"name": "nextContext"
},

View File

@ -341,6 +341,9 @@
{
"name": "isProceduralRenderer"
},
{
"name": "isRootView"
},
{
"name": "leaveView"
},
@ -353,6 +356,9 @@
{
"name": "nativeInsertBefore"
},
{
"name": "nativeParentNode"
},
{
"name": "nextNgElementId"
},

View File

@ -3803,6 +3803,9 @@
{
"name": "isQuote"
},
{
"name": "isRootView"
},
{
"name": "isScheduler"
},
@ -3956,6 +3959,12 @@
{
"name": "nativeInsertBefore"
},
{
"name": "nativeNextSibling"
},
{
"name": "nativeParentNode"
},
{
"name": "needsAdditionalRootNode"
},

View File

@ -893,6 +893,9 @@
{
"name": "isProceduralRenderer"
},
{
"name": "isRootView"
},
{
"name": "isStylingContext"
},
@ -938,6 +941,12 @@
{
"name": "nativeInsertBefore"
},
{
"name": "nativeNextSibling"
},
{
"name": "nativeParentNode"
},
{
"name": "nextContext"
},

View File

@ -2123,6 +2123,9 @@
{
"name": "isPromise$2"
},
{
"name": "isRootView"
},
{
"name": "isScheduler"
},
@ -2219,6 +2222,12 @@
{
"name": "nativeInsertBefore"
},
{
"name": "nativeNextSibling"
},
{
"name": "nativeParentNode"
},
{
"name": "nextContext"
},

View File

@ -2677,6 +2677,8 @@ class MockRenderer implements ProceduralRenderer3 {
selectRootElement(selectorOrNode: string|any): RElement {
return ({} as any);
}
parentNode(node: RNode): RElement|null { return node.parentNode as RElement; }
nextSibling(node: RNode): RNode|null { return node.nextSibling; }
setAttribute(el: RElement, name: string, value: string, namespace?: string|null): void {}
removeAttribute(el: RElement, name: string, namespace?: string|null): void {}
addClass(el: RElement, name: string): void {}

View File

@ -36,17 +36,40 @@ import {Type} from '../../src/type';
import {getRendererFactory2} from './imported_renderer2';
export abstract class BaseFixture {
/**
* Each fixture creates the following initial DOM structure:
* <div fixture="mark">
* <div host="mark"></div>
* </div>
*
* Components are bootstrapped into the <div host="mark"></div>.
* The <div fixture="mark"> is there for cases where the root component creates DOM node _outside_
* of its host element (for example when the root component injectes ViewContainerRef or does
* low-level DOM manipulation).
*
* The <div fixture="mark"> is _not_ attached to the document body.
*/
containerElement: HTMLElement;
hostElement: HTMLElement;
constructor() {
this.containerElement = document.createElement('div');
this.containerElement.setAttribute('fixture', 'mark');
this.hostElement = document.createElement('div');
this.hostElement.setAttribute('fixture', 'mark');
this.hostElement.setAttribute('host', 'mark');
this.containerElement.appendChild(this.hostElement);
}
/**
* Current state of rendered HTML.
* Current state of HTML rendered by the bootstrapped component.
*/
get html(): string { return toHtml(this.hostElement as any as Element); }
/**
* Current state of HTML rendered by the fixture (will include HTML rendered by the bootstrapped
* component as well as any elements outside of the component's host).
*/
get outerHtml(): string { return toHtml(this.containerElement as any as Element); }
}
function noop() {}
@ -251,9 +274,9 @@ export function toHtml<T>(componentOrElement: T | RElement): string {
if (element) {
return stringifyElement(element)
.replace(/^<div host="">/, '')
.replace(/^<div fixture="mark">/, '')
.replace(/<\/div>$/, '')
.replace(/^<div host="">(.*)<\/div>$/, '$1')
.replace(/^<div fixture="mark">(.*)<\/div>$/, '$1')
.replace(/^<div host="mark">(.*)<\/div>$/, '$1')
.replace(' style=""', '')
.replace(/<!--container-->/g, '')
.replace(/<!--ng-container-->/g, '');

View File

@ -1768,4 +1768,52 @@ describe('ViewContainerRef', () => {
});
});
describe('view engine compatibility', () => {
// https://stackblitz.com/edit/angular-xxpffd?file=src%2Findex.html
it('should allow injecting VCRef into the root (bootstrapped) component', () => {
const DynamicComponent =
createComponent('dynamic-cmpt', function(rf: RenderFlags, parent: any) {
if (rf & RenderFlags.Create) {
text(0, 'inserted dynamically');
}
}, 1, 0);
@Component({selector: 'app', template: ''})
class AppCmpt {
static ngComponentDef = defineComponent({
type: AppCmpt,
selectors: [['app']],
factory: () => new AppCmpt(
directiveInject(ViewContainerRef as any), injectComponentFactoryResolver()),
consts: 0,
vars: 0,
template: (rf: RenderFlags, cmp: AppCmpt) => {}
});
constructor(
private _vcRef: ViewContainerRef, private _cfResolver: ComponentFactoryResolver) {}
insert() {
this._vcRef.createComponent(this._cfResolver.resolveComponentFactory(DynamicComponent));
}
clear() { this._vcRef.clear(); }
}
const fixture = new ComponentFixture(AppCmpt);
expect(fixture.outerHtml).toBe('<div host="mark"></div>');
fixture.component.insert();
fixture.update();
expect(fixture.outerHtml)
.toBe('<div host="mark"></div><dynamic-cmpt>inserted dynamically</dynamic-cmpt>');
fixture.component.clear();
fixture.update();
expect(fixture.outerHtml).toBe('<div host="mark"></div>');
});
});
});