diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts
index 82d08ed6e2..98048b7546 100644
--- a/packages/core/test/acceptance/view_container_ref_spec.ts
+++ b/packages/core/test/acceptance/view_container_ref_spec.ts
@@ -6,8 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Component, Directive, ElementRef, NO_ERRORS_SCHEMA, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
-import {TestBed} from '@angular/core/testing';
+import {CommonModule, DOCUMENT} from '@angular/common';
+import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
+import {Input} from '@angular/core/src/metadata';
+import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
+import {TestBed, TestComponentRenderer} from '@angular/core/testing';
+import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
@@ -21,6 +25,15 @@ describe('ViewContainerRef', () => {
'{$startTagDiv}{$closeTagDiv}{$startTagBefore}{$closeTagBefore}'
};
+ /**
+ * Gets the inner HTML of the given element with all HTML comments and Angular internal
+ * reflect attributes omitted. This makes HTML comparisons easier and less verbose.
+ */
+ function getElementHtml(element: Element) {
+ return element.innerHTML.replace(//g, '')
+ .replace(/\sng-reflect-\S*="[^"]*"/g, '');
+ }
+
beforeEach(() => {
ɵi18nConfigureLocalize({translations: TRANSLATIONS});
TestBed.configureTestingModule({
@@ -269,8 +282,1418 @@ describe('ViewContainerRef', () => {
});
});
+ describe('length', () => {
+ it('should return the number of embedded views', () => {
+ TestBed.configureTestingModule({declarations: [EmbeddedViewInsertionComp, VCRefDirective]});
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(vcRefDir.vcref.length).toEqual(0);
+
+ vcRefDir.createView('A');
+ vcRefDir.createView('B');
+ vcRefDir.createView('C');
+ fixture.detectChanges();
+ expect(vcRefDir.vcref.length).toEqual(3);
+
+ vcRefDir.vcref.detach(1);
+ fixture.detectChanges();
+ expect(vcRefDir.vcref.length).toEqual(2);
+
+ vcRefDir.vcref.clear();
+ fixture.detectChanges();
+ expect(vcRefDir.vcref.length).toEqual(0);
+ });
+ });
+
+ describe('get and indexOf', () => {
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({declarations: [EmbeddedViewInsertionComp, VCRefDirective]});
+ });
+
+ it('should retrieve a ViewRef from its index, and vice versa', () => {
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ vcRefDir.createView('A');
+ vcRefDir.createView('B');
+ vcRefDir.createView('C');
+ fixture.detectChanges();
+
+ let viewRef = vcRefDir.vcref.get(0);
+ expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(0);
+
+ viewRef = vcRefDir.vcref.get(1);
+ expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(1);
+
+ viewRef = vcRefDir.vcref.get(2);
+ expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(2);
+ });
+
+ it('should handle out of bounds cases', () => {
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ vcRefDir.createView('A');
+ fixture.detectChanges();
+
+ expect(vcRefDir.vcref.get(-1)).toBeNull();
+ expect(vcRefDir.vcref.get(42)).toBeNull();
+
+ const viewRef = vcRefDir.vcref.get(0);
+ vcRefDir.vcref.remove(0);
+ expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(-1);
+ });
+ });
+
+ describe('move', () => {
+ it('should move embedded views and associated DOM nodes without recreating them', () => {
+ TestBed.configureTestingModule({declarations: [EmbeddedViewInsertionComp, VCRefDirective]});
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ vcRefDir.createView('A');
+ vcRefDir.createView('B');
+ vcRefDir.createView('C');
+
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('
ABC');
+
+ // The DOM is manually modified here to ensure that the text node is actually moved
+ fixture.nativeElement.childNodes[2].nodeValue = '**A**';
+ expect(getElementHtml(fixture.nativeElement)).toEqual('**A**BC');
+
+ let viewRef = vcRefDir.vcref.get(0);
+ vcRefDir.vcref.move(viewRef !, 2);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('BC**A**');
+
+ vcRefDir.vcref.move(viewRef !, 0);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('**A**BC');
+
+ vcRefDir.vcref.move(viewRef !, 1);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('B**A**C');
+
+ // Invalid indices when detaching throws an exception in Ivy: FW-1330.
+ ivyEnabled && expect(() => vcRefDir.vcref.move(viewRef !, -1)).toThrow();
+ ivyEnabled && expect(() => vcRefDir.vcref.move(viewRef !, 42)).toThrow();
+ });
+ });
+
+ describe('getters', () => {
+ it('should work on templates', () => {
+ @Component({
+ template: `
+ {{name}}
+
+ `
+ })
+ class TestComponent {
+ @ViewChild(VCRefDirective, {static: true}) vcRefDir !: VCRefDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [VCRefDirective, TestComponent]});
+ const fixture = TestBed.createComponent(TestComponent);
+ const {vcRefDir} = fixture.componentInstance;
+ fixture.detectChanges();
+
+ expect(vcRefDir.vcref.element.nativeElement.nodeType).toBe(Node.COMMENT_NODE);
+ // In Ivy, the comment for the view container ref has text that implies
+ // that the comment is a placeholder for a container.
+ ivyEnabled && expect(vcRefDir.vcref.element.nativeElement.textContent).toEqual('container');
+
+ expect(vcRefDir.vcref.injector.get(ElementRef).nativeElement.textContent);
+ expect(getElementHtml(vcRefDir.vcref.parentInjector.get(ElementRef).nativeElement))
+ .toBe('');
+ });
+ });
+
+ describe('detach', () => {
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({declarations: [EmbeddedViewInsertionComp, VCRefDirective]});
+
+ // Tests depend on perf counters when running with Ivy. In order to have
+ // clean perf counters at the beginning of a test, we reset those here.
+ ivyEnabled && ngDevModeResetPerfCounters();
+ });
+
+ it('should detach the right embedded view when an index is specified', () => {
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ const viewA = vcRefDir.createView('A');
+ vcRefDir.createView('B');
+ vcRefDir.createView('C');
+ const viewD = vcRefDir.createView('D');
+ vcRefDir.createView('E');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCDE');
+
+ vcRefDir.vcref.detach(3);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCE');
+ expect(viewD.destroyed).toBeFalsy();
+
+ vcRefDir.vcref.detach(0);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('BCE');
+ expect(viewA.destroyed).toBeFalsy();
+
+ // Invalid indices when detaching throws an exception in Ivy: FW-1330.
+ ivyEnabled && expect(() => vcRefDir.vcref.detach(-1)).toThrow();
+ ivyEnabled && expect(() => vcRefDir.vcref.detach(42)).toThrow();
+ ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(0);
+ });
+
+ it('should detach the last embedded view when no index is specified', () => {
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ vcRefDir.createView('A');
+ vcRefDir.createView('B');
+ vcRefDir.createView('C');
+ vcRefDir.createView('D');
+ const viewE = vcRefDir.createView('E');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCDE');
+
+ vcRefDir.vcref.detach();
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCD');
+ expect(viewE.destroyed).toBeFalsy();
+ ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(0);
+ });
+ });
+
+ describe('remove', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({declarations: [EmbeddedViewInsertionComp, VCRefDirective]});
+
+ const _origRendererFactory = TestBed.get(RendererFactory2) as RendererFactory2;
+ const _origCreateRenderer = _origRendererFactory.createRenderer;
+
+ _origRendererFactory.createRenderer = function() {
+ const renderer = _origCreateRenderer.apply(_origRendererFactory, arguments);
+ renderer.destroyNode = () => {};
+ return renderer;
+ };
+
+ // Tests depend on perf counters when running with Ivy. In order to have
+ // clean perf counters at the beginning of a test, we reset those here.
+ ivyEnabled && ngDevModeResetPerfCounters();
+ });
+
+ it('should remove the right embedded view when an index is specified', () => {
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ const viewA = vcRefDir.createView('A');
+ vcRefDir.createView('B');
+ vcRefDir.createView('C');
+ const viewD = vcRefDir.createView('D');
+ vcRefDir.createView('E');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCDE');
+
+ vcRefDir.vcref.remove(3);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCE');
+ expect(viewD.destroyed).toBeTruthy();
+
+ vcRefDir.vcref.remove(0);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('BCE');
+ expect(viewA.destroyed).toBeTruthy();
+
+ // Invalid indices when detaching throws an exception in Ivy: FW-1330.
+ ivyEnabled && expect(() => vcRefDir.vcref.remove(-1)).toThrow();
+ ivyEnabled && expect(() => vcRefDir.vcref.remove(42)).toThrow();
+ ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(2);
+ });
+
+ it('should remove the last embedded view when no index is specified', () => {
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ vcRefDir.createView('A');
+ vcRefDir.createView('B');
+ vcRefDir.createView('C');
+ vcRefDir.createView('D');
+ const viewE = vcRefDir.createView('E');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCDE');
+
+ vcRefDir.vcref.remove();
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABCD');
+ expect(viewE.destroyed).toBeTruthy();
+ ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(1);
+ });
+
+ it('should throw when trying to insert a removed or destroyed view', () => {
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ const viewA = vcRefDir.createView('A');
+ const viewB = vcRefDir.createView('B');
+ fixture.detectChanges();
+
+ vcRefDir.vcref.remove();
+ fixture.detectChanges();
+ expect(() => vcRefDir.vcref.insert(viewB)).toThrow();
+
+ viewA.destroy();
+ fixture.detectChanges();
+ expect(() => vcRefDir.vcref.insert(viewA)).toThrow();
+ });
+ });
+
+ describe('createEmbeddedView (incl. insert)', () => {
+ it('should work on elements', () => {
+ @Component({
+ template: `
+ {{name}}
+
+
+ `,
+ })
+ class TestComponent {
+ }
+
+ TestBed.configureTestingModule({declarations: [TestComponent, VCRefDirective]});
+
+ const fixture = TestBed.createComponent(TestComponent);
+ const vcRef =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('');
+
+ vcRef.createView('A');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('A');
+
+ vcRef.createView('B');
+ vcRef.createView('C');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('ABC');
+
+ vcRef.createView('Y', 0);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('YABC');
+
+ // Invalid indices when detaching throws an exception in Ivy: FW-1330.
+ ivyEnabled && expect(() => vcRef.createView('Z', -1)).toThrow();
+ ivyEnabled && expect(() => vcRef.createView('Z', 5)).toThrow();
+ });
+
+ it('should work on components', () => {
+ @Component({selector: 'header-cmp', template: ``})
+ class HeaderComponent {
+ }
+
+ @Component({
+ template: `
+ {{name}}
+
+
+ `,
+ })
+ class TestComponent {
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [TestComponent, HeaderComponent, VCRefDirective]});
+ const fixture = TestBed.createComponent(TestComponent);
+ const vcRef =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('');
+
+ vcRef.createView('A');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('A');
+
+ vcRef.createView('B');
+ vcRef.createView('C');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('ABC');
+
+ vcRef.createView('Y', 0);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('YABC');
+
+ // Invalid indices when detaching throws an exception in Ivy: FW-1330.
+ ivyEnabled && expect(() => vcRef.createView('Z', -1)).toThrow();
+ ivyEnabled && expect(() => vcRef.createView('Z', 5)).toThrow();
+ });
+
+ it('should work with multiple instances of view container refs', () => {
+ @Component({
+ template: `
+ {{name}}
+
+
+ `,
+ })
+ class TestComponent {
+ }
+
+ TestBed.configureTestingModule({declarations: [TestComponent, VCRefDirective]});
+ const fixture = TestBed.createComponent(TestComponent);
+ const vcRefs = fixture.debugElement.queryAll(By.directive(VCRefDirective))
+ .map(debugEl => debugEl.injector.get(VCRefDirective));
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('');
+
+ vcRefs[0].createView('A');
+ vcRefs[1].createView('B');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('AB');
+ });
+
+ it('should work on templates', () => {
+ @Component({
+ template: `
+ {{name}}
+
+ `
+ })
+ class TestComponent {
+ @ViewChild(VCRefDirective, {static: true}) vcRef !: VCRefDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [TestComponent, VCRefDirective]});
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+ const {vcRef} = fixture.componentInstance;
+
+ expect(getElementHtml(fixture.nativeElement)).toEqual('');
+
+ vcRef.createView('A');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('A');
+
+ vcRef.createView('B');
+ vcRef.createView('C');
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('ABC');
+
+ vcRef.createView('Y', 0);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('YABC');
+
+ // Invalid indices when detaching throws an exception in Ivy: FW-1330.
+ ivyEnabled && expect(() => vcRef !.createView('Z', -1)).toThrow();
+ ivyEnabled && expect(() => vcRef !.createView('Z', 5)).toThrow();
+ });
+
+ it('should apply directives and pipes of the host view to the TemplateRef', () => {
+ @Component({selector: 'child', template: `{{name}}`})
+ class Child {
+ @Input() name: string|undefined;
+ }
+
+ @Pipe({name: 'starPipe'})
+ class StarPipe implements PipeTransform {
+ transform(value: any) { return `**${value}**`; }
+ }
+
+ @Component({
+ template: `
+
+
+
+
+
+ `
+ })
+ class SomeComponent {
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [Child, StarPipe, SomeComponent, VCRefDirective]});
+ const fixture = TestBed.createComponent(SomeComponent);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
+ vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '**A****C****C****B**');
+ });
+
+ });
+
+ describe('createComponent', () => {
+ let templateExecutionCounter = 0;
+
+ beforeEach(() => templateExecutionCounter = 0);
+
+ it('should work without Injector and NgModuleRef', () => {
+ @Component({selector: 'embedded-cmp', template: `foo`})
+ class EmbeddedComponent implements DoCheck, OnInit {
+ ngOnInit() { templateExecutionCounter++; }
+
+ ngDoCheck() { templateExecutionCounter++; }
+ }
+
+ @NgModule({entryComponents: [EmbeddedComponent], declarations: [EmbeddedComponent]})
+ class EmbeddedComponentModule {
+ }
+
+ TestBed.configureTestingModule({
+ declarations: [EmbeddedViewInsertionComp, VCRefDirective],
+ imports: [EmbeddedComponentModule]
+ });
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement)).toEqual('');
+ expect(templateExecutionCounter).toEqual(0);
+
+ const componentRef =
+ vcRefDir.vcref.createComponent(vcRefDir.cfr.resolveComponentFactory(EmbeddedComponent));
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('foo');
+ expect(templateExecutionCounter).toEqual(2);
+
+ vcRefDir.vcref.detach(0);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual('');
+ expect(templateExecutionCounter).toEqual(2);
+
+ vcRefDir.vcref.insert(componentRef.hostView);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('foo');
+ expect(templateExecutionCounter).toEqual(3);
+ });
+
+ it('should work with NgModuleRef and Injector', () => {
+ @Component({
+ selector: 'embedded-cmp',
+ template: `foo`,
+ })
+ class EmbeddedComponent implements DoCheck,
+ OnInit {
+ constructor(public s: String) {}
+
+ ngOnInit() { templateExecutionCounter++; }
+
+ ngDoCheck() { templateExecutionCounter++; }
+ }
+
+ @NgModule({entryComponents: [EmbeddedComponent], declarations: [EmbeddedComponent]})
+ class EmbeddedComponentModule {
+ }
+
+ TestBed.configureTestingModule({
+ declarations: [EmbeddedViewInsertionComp, VCRefDirective],
+ imports: [EmbeddedComponentModule]
+ });
+
+ @NgModule({
+ providers: [
+ {provide: String, useValue: 'root_module'},
+ // We need to provide the following tokens because otherwise view engine
+ // will throw when creating a component factory in debug mode.
+ {provide: Sanitizer, useValue: TestBed.get(Sanitizer)},
+ {provide: ErrorHandler, useValue: TestBed.get(ErrorHandler)},
+ {provide: RendererFactory2, useValue: TestBed.get(RendererFactory2)},
+ ]
+ })
+ class MyAppModule {
+ }
+
+ @NgModule({providers: [{provide: String, useValue: 'some_module'}]})
+ class SomeModule {
+ }
+
+ // Compile test modules in order to be able to pass the NgModuleRef or the
+ // module injector to the ViewContainerRef create component method.
+ const compiler = TestBed.get(Compiler) as Compiler;
+ const appModuleFactory = compiler.compileModuleSync(MyAppModule);
+ const someModuleFactory = compiler.compileModuleSync(SomeModule);
+ const appModuleRef = appModuleFactory.create(null);
+ const someModuleRef = someModuleFactory.create(null);
+
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement)).toEqual('');
+ expect(templateExecutionCounter).toEqual(0);
+
+ let componentRef = vcRefDir.vcref.createComponent(
+ vcRefDir.cfr.resolveComponentFactory(EmbeddedComponent), 0, someModuleRef.injector);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('foo');
+ expect(templateExecutionCounter).toEqual(2);
+ expect(componentRef.instance.s).toEqual('some_module');
+
+ componentRef = vcRefDir.vcref.createComponent(
+ vcRefDir.cfr.resolveComponentFactory(EmbeddedComponent), 0, undefined, undefined,
+ appModuleRef);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'foofoo');
+ expect(componentRef.instance.s).toEqual('root_module');
+ expect(templateExecutionCounter).toEqual(5);
+ });
+
+ it('should support projectable nodes', () => {
+ TestBed.configureTestingModule({
+ declarations: [EmbeddedViewInsertionComp, VCRefDirective],
+ imports: [EmbeddedComponentWithNgZoneModule]
+ });
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement)).toEqual('');
+
+ const myNode = document.createElement('div');
+ const myText = document.createTextNode('bar');
+ const myText2 = document.createTextNode('baz');
+ myNode.appendChild(myText);
+ myNode.appendChild(myText2);
+
+ vcRefDir.vcref.createComponent(
+ vcRefDir.cfr.resolveComponentFactory(EmbeddedComponentWithNgContent), 0, undefined,
+ [[myNode]]);
+ fixture.detectChanges();
+
+ // With Ivy the projected content is inserted into the last ng-content container,
+ // while with View Engine the content is projected into the first ng-content slot.
+ // View Engine correctly respects the passed index of "projectedNodes". See: FW-1331.
+ if (ivyEnabled) {
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '
barbaz
');
+ } else {
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'barbaz
');
+ }
+ });
+
+ it('should support reprojection of projectable nodes', () => {
+ @Component({
+ selector: 'reprojector',
+ template:
+ ``,
+ })
+ class Reprojector {
+ }
+
+ @NgModule({
+ exports: [Reprojector, EmbeddedComponentWithNgContent],
+ declarations: [Reprojector, EmbeddedComponentWithNgContent],
+ entryComponents: [Reprojector]
+ })
+ class ReprojectorModule {
+ }
+
+ TestBed.configureTestingModule({
+ declarations: [EmbeddedViewInsertionComp, VCRefDirective],
+ imports: [ReprojectorModule]
+ });
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement)).toEqual('');
+
+ const myNode = document.createElement('div');
+ const myText = document.createTextNode('bar');
+ const myText2 = document.createTextNode('baz');
+ myNode.appendChild(myText);
+ myNode.appendChild(myText2);
+
+ vcRefDir.vcref.createComponent(
+ vcRefDir.cfr.resolveComponentFactory(Reprojector), 0, undefined, [[myNode]]);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '
barbaz
');
+ });
+
+ it('should support many projectable nodes with many slots', () => {
+ TestBed.configureTestingModule({
+ declarations: [EmbeddedViewInsertionComp, VCRefDirective],
+ imports: [EmbeddedComponentWithNgZoneModule]
+ });
+ const fixture = TestBed.createComponent(EmbeddedViewInsertionComp);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement)).toEqual('');
+
+ vcRefDir.vcref.createComponent(
+ vcRefDir.cfr.resolveComponentFactory(EmbeddedComponentWithNgContent), 0, undefined, [
+ [document.createTextNode('1'), document.createTextNode('2')],
+ [document.createTextNode('3'), document.createTextNode('4')]
+ ]);
+ fixture.detectChanges();
+
+ // With Ivy multi-slot projection is currently not working. This is a bug that
+ // is tracked with FW-1333.
+ if (ivyEnabled) {
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '
12');
+ } else {
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '12
34');
+ }
+ });
+ });
+
+ describe('insertion points and declaration points', () => {
+ @Directive({selector: '[tplDir]'})
+ class InsertionDir {
+ @Input()
+ set tplDir(tpl: TemplateRef|null) {
+ tpl ? this.vcr.createEmbeddedView(tpl) : this.vcr.clear();
+ }
+
+ constructor(public vcr: ViewContainerRef) {}
+ }
+
+ // see running stackblitz example: https://stackblitz.com/edit/angular-w3myy6
+ it('should work with a template declared in a different component view from insertion', () => {
+ @Component({selector: 'child', template: `{{name}}
`})
+ class Child {
+ @Input() tpl: TemplateRef|null = null;
+ name = 'Child';
+ }
+
+ @Component({
+ template: `
+
+ {{name}}
+
+
+
+ `
+ })
+ class Parent {
+ name = 'Parent';
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent, InsertionDir]});
+ const fixture = TestBed.createComponent(Parent);
+ const child = fixture.debugElement.query(By.directive(Child)).componentInstance;
+ fixture.detectChanges();
+
+ // Context should be inherited from the declaration point, not the
+ // insertion point, so the template should read 'Parent'.
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(`Child
Parent
`);
+
+ child.tpl = null;
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toEqual(`Child
`);
+ });
+
+ // see running stackblitz example: https://stackblitz.com/edit/angular-3vplec
+ it('should work with nested for loops with different declaration / insertion points', () => {
+ @Component({
+ selector: 'loop-comp',
+ template: `
+
+
+ `,
+ })
+ class LoopComp {
+ @Input() tpl !: TemplateRef;
+ @Input() rows !: any[];
+ name = 'Loop';
+ }
+
+ @Component({
+ template: `
+
+
+ {{cell}} - {{row.value}} - {{name}}
+
+
+
+
+
+
+ `,
+ })
+ class Parent {
+ name = 'Parent';
+ rows = [{data: ['1', '2'], value: 'one'}, {data: ['3', '4'], value: 'two'}];
+ }
+
+ TestBed.configureTestingModule({declarations: [LoopComp, Parent], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '' +
+ '1 - one - Parent
2 - one - Parent
' +
+ '3 - two - Parent
4 - two - Parent
' +
+ '');
+
+ fixture.componentInstance.rows =
+ [{data: ['5', '6'], value: 'three'}, {data: ['7'], value: 'four'}];
+ fixture.componentInstance.name = 'New name!';
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '' +
+ '5 - three - New name!
6 - three - New name!
' +
+ '7 - four - New name!
' +
+ '');
+ });
+
+ });
+
+ describe('lifecycle hooks', () => {
+
+ // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
+ const log: string[] = [];
+
+ @Component({selector: 'hooks', template: `{{name}}`})
+ class ComponentWithHooks {
+ @Input() name: string|undefined;
+
+ private log(msg: string) { log.push(msg); }
+
+ ngOnChanges() { this.log('onChanges-' + this.name); }
+ ngOnInit() { this.log('onInit-' + this.name); }
+ ngDoCheck() { this.log('doCheck-' + this.name); }
+
+ ngAfterContentInit() { this.log('afterContentInit-' + this.name); }
+ ngAfterContentChecked() { this.log('afterContentChecked-' + this.name); }
+
+ ngAfterViewInit() { this.log('afterViewInit-' + this.name); }
+ ngAfterViewChecked() { this.log('afterViewChecked-' + this.name); }
+
+ ngOnDestroy() { this.log('onDestroy-' + this.name); }
+ }
+
+ @NgModule({
+ declarations: [ComponentWithHooks],
+ exports: [ComponentWithHooks],
+ entryComponents: [ComponentWithHooks]
+ })
+ class ComponentWithHooksModule {
+ }
+
+ it('should call all hooks in correct order when creating with createEmbeddedView', () => {
+ @Component({
+ template: `
+
+
+
+
+
+ `
+ })
+ class SomeComponent {
+ }
+
+ log.length = 0;
+
+ TestBed.configureTestingModule({
+ declarations: [SomeComponent, ComponentWithHooks, VCRefDirective],
+ });
+ const fixture = TestBed.createComponent(SomeComponent);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'onChanges-A', 'onInit-A', 'doCheck-A', 'onChanges-B', 'onInit-B', 'doCheck-B',
+ 'afterContentInit-A', 'afterContentChecked-A', 'afterContentInit-B',
+ 'afterContentChecked-B', 'afterViewInit-A', 'afterViewChecked-A', 'afterViewInit-B',
+ 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
+ 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('AB');
+ expect(log).toEqual([]);
+
+ log.length = 0;
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('ACB');
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'onChanges-C', 'onInit-C', 'doCheck-C', 'afterContentInit-C',
+ 'afterContentChecked-C', 'afterViewInit-C', 'afterViewChecked-C', 'afterContentChecked-A',
+ 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'doCheck-C', 'afterContentChecked-C', 'afterViewChecked-C',
+ 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ const viewRef = vcRefDir.vcref.detach(0);
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
+ 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ vcRefDir.vcref.insert(viewRef !);
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'doCheck-C', 'afterContentChecked-C', 'afterViewChecked-C',
+ 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ vcRefDir.vcref.remove(0);
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'onDestroy-C', 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
+ 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+ });
+
+ it('should call all hooks in correct order when creating with createComponent', () => {
+ @Component({
+ template: `
+
+
+ `
+ })
+ class SomeComponent {
+ }
+
+ log.length = 0;
+
+ TestBed.configureTestingModule(
+ {declarations: [SomeComponent, VCRefDirective], imports: [ComponentWithHooksModule]});
+ const fixture = TestBed.createComponent(SomeComponent);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'onChanges-A', 'onInit-A', 'doCheck-A', 'onChanges-B', 'onInit-B', 'doCheck-B',
+ 'afterContentInit-A', 'afterContentChecked-A', 'afterContentInit-B',
+ 'afterContentChecked-B', 'afterViewInit-A', 'afterViewChecked-A', 'afterViewInit-B',
+ 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
+ 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ const componentRef =
+ vcRefDir.vcref.createComponent(vcRefDir.cfr.resolveComponentFactory(ComponentWithHooks));
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('AB');
+ expect(log).toEqual([]);
+
+ componentRef.instance.name = 'D';
+ log.length = 0;
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('ADB');
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'onInit-D', 'doCheck-D', 'afterContentInit-D',
+ 'afterContentChecked-D', 'afterViewInit-D', 'afterViewChecked-D', 'afterContentChecked-A',
+ 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'doCheck-D', 'afterContentChecked-D', 'afterViewChecked-D',
+ 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ const viewRef = vcRefDir.vcref.detach(0);
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
+ 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ vcRefDir.vcref.insert(viewRef !);
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'doCheck-A', 'doCheck-B', 'doCheck-D', 'afterContentChecked-D', 'afterViewChecked-D',
+ 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+
+ log.length = 0;
+ vcRefDir.vcref.remove(0);
+ fixture.detectChanges();
+ expect(log).toEqual([
+ 'onDestroy-D', 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
+ 'afterViewChecked-A', 'afterViewChecked-B'
+ ]);
+ });
+ });
+
+ describe('host bindings', () => {
+
+ it('should support host bindings on dynamically created components', () => {
+ @Component(
+ {selector: 'host-bindings', host: {'id': 'attribute', '[title]': 'title'}, template: ``})
+ class HostBindingCmpt {
+ title = 'initial';
+ }
+
+ @Component({template: ``})
+ class TestComponent {
+ @ViewChild(VCRefDirective, {static: true}) vcRefDir !: VCRefDirective;
+ }
+
+ @NgModule({declarations: [HostBindingCmpt], entryComponents: [HostBindingCmpt]})
+ class TestModule {
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [TestComponent, VCRefDirective], imports: [TestModule]});
+ const fixture = TestBed.createComponent(TestComponent);
+ const {vcRefDir} = fixture.componentInstance;
+
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toBe('');
+
+ const componentRef =
+ vcRefDir.vcref.createComponent(vcRefDir.cfr.resolveComponentFactory(HostBindingCmpt));
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.children[0].tagName).toBe('HOST-BINDINGS');
+ expect(fixture.nativeElement.children[0].getAttribute('id')).toBe('attribute');
+ expect(fixture.nativeElement.children[0].getAttribute('title')).toBe('initial');
+
+ componentRef.instance.title = 'changed';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.children[0].tagName).toBe('HOST-BINDINGS');
+ expect(fixture.nativeElement.children[0].getAttribute('id')).toBe('attribute');
+ expect(fixture.nativeElement.children[0].getAttribute('title')).toBe('changed');
+ });
+
+ });
+
+ describe('projection', () => {
+
+ it('should project the ViewContainerRef content along its host, in an element', () => {
+ @Component({selector: 'child', template: '
'})
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+ {{name}}
+
+
+
+
+ `
+ })
+ class Parent {
+ name: string = 'bar';
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent, VCRefDirective]});
+ const fixture = TestBed.createComponent(Parent);
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('');
+
+ vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('bar
');
+ });
+
+ it('should project the ViewContainerRef content along its host, in a view', () => {
+ @Component({
+ selector: 'child-with-view',
+ template: `Before (inside)--After (inside)`
+ })
+ class ChildWithView {
+ show: boolean = true;
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+ {{name}}
+
+
+ Before projected
+
+ After projected
+ `
+ })
+ class Parent {
+ name: string = 'bar';
+ }
+
+ TestBed.configureTestingModule({declarations: [ChildWithView, Parent, VCRefDirective]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+ const vcRefDir =
+ fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'Before (inside)- Before projected After projected -After (inside)');
+
+ vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'Before (inside)- Before projected bar After projected -After (inside)');
+ });
+
+ describe('with select', () => {
+
+ @Component({
+ selector: 'child-with-selector',
+ template: `
+
+
`,
+ })
+ class ChildWithSelector {
+ }
+
+ it('should project the ViewContainerRef content along its host, when the host matches a selector',
+ () => {
+ @Component({
+ selector: 'parent',
+ template: `
+
+ {{name}}
+
+
+
+
+ `
+ })
+ class Parent {
+ name: string = 'bar';
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [Parent, ChildWithSelector, VCRefDirective]});
+ const fixture = TestBed.createComponent(Parent);
+ const vcRefDir = fixture.debugElement.query(By.directive(VCRefDirective))
+ .injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '');
+
+ vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'bar
');
+ });
+
+ it('should not project the ViewContainerRef content, when the host does not match a selector',
+ () => {
+ @Component({
+ selector: 'parent',
+ template: `
+
+ {{name}}
+
+
+
+
+ `
+ })
+ class Parent {
+ name: string = 'bar';
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [Parent, ChildWithSelector, VCRefDirective]});
+ const fixture = TestBed.createComponent(Parent);
+ const vcRefDir = fixture.debugElement.query(By.directive(VCRefDirective))
+ .injector.get(VCRefDirective);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '');
+
+ vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'bar
');
+ });
+ });
+
+ });
+
+ describe('root view container ref', () => {
+ let containerEl: HTMLElement|null = null;
+
+ beforeEach(() => containerEl = null);
+
+ /**
+ * Creates a new test component renderer instance that wraps the root element
+ * in another element. This allows us to test if elements have been inserted into
+ * the parent element of the root component.
+ */
+ function createTestComponentRenderer(document: any): TestComponentRenderer {
+ return {
+ insertRootElement(rootElementId: string) {
+ const rootEl = document.createElement('div');
+ rootEl.id = rootElementId;
+
+ containerEl = document.createElement('div');
+ document.body.appendChild(containerEl);
+ containerEl !.appendChild(rootEl);
+ }
+ };
+ }
+
+ const TEST_COMPONENT_RENDERER = {
+ provide: TestComponentRenderer,
+ useFactory: createTestComponentRenderer,
+ deps: [DOCUMENT]
+ };
+
+ it('should check bindings for components dynamically created by root component', () => {
+ @Component({
+ selector: 'dynamic-cmpt-with-bindings',
+ template: `check count: {{checkCount}}`,
+ })
+ class DynamicCompWithBindings implements DoCheck {
+ checkCount = 0;
+
+ ngDoCheck() { this.checkCount++; }
+ }
+
+ @Component({template: ``})
+ class TestComp {
+ constructor(public vcRef: ViewContainerRef, public cfResolver: ComponentFactoryResolver) {}
+ }
+
+ @NgModule(
+ {entryComponents: [DynamicCompWithBindings], declarations: [DynamicCompWithBindings]})
+ class DynamicCompWithBindingsModule {
+ }
+
+
+ TestBed.configureTestingModule({
+ declarations: [TestComp],
+ imports: [DynamicCompWithBindingsModule],
+ providers: [TEST_COMPONENT_RENDERER]
+ });
+ const fixture = TestBed.createComponent(TestComp);
+ const {vcRef, cfResolver} = fixture.componentInstance;
+ fixture.detectChanges();
+
+ // Ivy inserts a comment for the root view container ref instance. This is not
+ // the case for view engine and we need to adjust the assertions.
+ expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 2 : 1);
+ ivyEnabled && expect(containerEl !.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);
+
+ expect((containerEl !.childNodes[0] as Element).tagName).toBe('DIV');
+
+ vcRef.createComponent(cfResolver.resolveComponentFactory(DynamicCompWithBindings));
+ fixture.detectChanges();
+
+ expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
+ expect(containerEl !.childNodes[1].textContent).toBe('check count: 1');
+
+ fixture.detectChanges();
+
+ expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
+ expect(containerEl !.childNodes[1].textContent).toBe('check count: 2');
+ });
+
+ it('should create deep DOM tree immediately for dynamically created components', () => {
+ @Component({template: ``})
+ class TestComp {
+ constructor(public vcRef: ViewContainerRef, public cfResolver: ComponentFactoryResolver) {}
+ }
+
+ @Component({selector: 'child', template: `{{name}}
`})
+ class Child {
+ name = 'text';
+ }
+
+ @Component({selector: 'dynamic-cmpt-with-children', template: ``})
+ class DynamicCompWithChildren {
+ }
+
+ @NgModule({
+ entryComponents: [DynamicCompWithChildren],
+ declarations: [DynamicCompWithChildren, Child]
+ })
+ class DynamicCompWithChildrenModule {
+ }
+
+ TestBed.configureTestingModule({
+ declarations: [TestComp],
+ imports: [DynamicCompWithChildrenModule],
+ providers: [TEST_COMPONENT_RENDERER]
+ });
+
+ const fixture = TestBed.createComponent(TestComp);
+ const {vcRef, cfResolver} = fixture.componentInstance;
+ fixture.detectChanges();
+
+ // Ivy inserts a comment for the root view container ref instance. This is not
+ // the case for view engine and we need to adjust the assertions.
+ expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 2 : 1);
+ ivyEnabled && expect(containerEl !.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);
+
+ expect((containerEl !.childNodes[0] as Element).tagName).toBe('DIV');
+
+ vcRef.createComponent(cfResolver.resolveComponentFactory(DynamicCompWithChildren));
+
+ expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
+ expect(getElementHtml(containerEl !.childNodes[1] as Element))
+ .toBe('');
+
+ fixture.detectChanges();
+
+ expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
+ expect(getElementHtml(containerEl !.childNodes[1] as Element))
+ .toBe(`text
`);
+ });
+ });
});
+@Component({
+ template: `
+ {{name}}
+
+ `,
+})
+class EmbeddedViewInsertionComp {
+}
+
+@Directive({
+ selector: '[vcref]',
+})
+class VCRefDirective {
+ @Input() tplRef: TemplateRef|undefined;
+ @Input() name: string = '';
+
+ // Injecting the ViewContainerRef to create a dynamic container in which
+ // embedded views will be created
+ constructor(public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
+
+ createView(s: string, index?: number): EmbeddedViewRef {
+ if (!this.tplRef) {
+ throw new Error('No template reference passed to directive.');
+ }
+
+ return this.vcref.createEmbeddedView(this.tplRef, {$implicit: s}, index);
+ }
+}
+
+@Component({
+ selector: `embedded-cmp-with-ngcontent`,
+ template: `
`
+})
+class EmbeddedComponentWithNgContent {
+}
+
+@NgModule({
+ exports: [EmbeddedComponentWithNgContent],
+ entryComponents: [EmbeddedComponentWithNgContent],
+ declarations: [EmbeddedComponentWithNgContent],
+})
+class EmbeddedComponentWithNgZoneModule {
+}
+
@Component({
selector: 'view-container-ref-comp',
template: `
diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts
index d0c1462cd7..bfdadb8472 100644
--- a/packages/core/test/render3/view_container_ref_spec.ts
+++ b/packages/core/test/render3/view_container_ref_spec.ts
@@ -6,23 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ComponentRef, ΔdefineInjector, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, ViewRef, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef,} from '../../src/core';
-import {createInjector} from '../../src/di/r3_injector';
+import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ComponentRef, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef,} from '../../src/core';
import {ViewEncapsulation} from '../../src/metadata';
-import {AttributeMarker, ΔdefineComponent, ΔdefineDirective, ΔdefinePipe, injectComponentFactoryResolver, Δlistener, ΔloadViewQuery, ΔNgOnChangesFeature, ΔqueryRefresh, ΔviewQuery,} from '../../src/render3/index';
+import {injectComponentFactoryResolver, ΔdefineComponent, ΔdefineDirective, Δlistener, ΔloadViewQuery, ΔqueryRefresh, ΔviewQuery,} from '../../src/render3/index';
-import {ΔallocHostVars, Δbind, Δcontainer, ΔcontainerRefreshEnd, ΔcontainerRefreshStart, ΔdirectiveInject, Δelement, ΔelementEnd, ΔelementHostAttrs, ΔelementProperty, ΔelementStart, ΔembeddedViewEnd, ΔembeddedViewStart, Δinterpolation1, Δinterpolation3, ΔnextContext, Δprojection, ΔprojectionDef, Δreference, Δtemplate, Δtext, ΔtextBinding,} from '../../src/render3/instructions/all';
+import {Δcontainer, ΔcontainerRefreshEnd, ΔcontainerRefreshStart, ΔdirectiveInject, Δelement, ΔelementEnd, ΔelementStart, ΔembeddedViewEnd, ΔembeddedViewStart, Δtemplate, Δtext,} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement} from '../../src/render3/interfaces/renderer';
-import {NgModuleFactory} from '../../src/render3/ng_module_ref';
-import {Δpipe, ΔpipeBind1} from '../../src/render3/pipe';
import {getLView} from '../../src/render3/state';
import {getNativeByIndex} from '../../src/render3/util/view_utils';
-import {ΔtemplateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound';
-import {NgForOf} from '../../test/render3/common_with_def';
-
-import {getRendererFactory2} from './imported_renderer2';
-import {ComponentFixture, createComponent, getDirectiveOnNode, TemplateFixture,} from './render_util';
+import {ComponentFixture, createComponent, TemplateFixture,} from './render_util';
const Component: typeof _Component = function(...args: any[]): any {
// In test we use @Component for documentation only so it's safe to mock out the implementation.
@@ -56,184 +49,8 @@ describe('ViewContainerRef', () => {
}
describe('API', () => {
- /**
- * {{name}}
- */
- function embeddedTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(0, Δbind(ctx.name));
- }
- }
-
- function createView(s: string, index?: number): EmbeddedViewRef {
- return directiveInstance !.vcref.createEmbeddedView(
- directiveInstance !.tplRef, {name: s}, index);
- }
-
- /**
- * {{name}}
- *
- */
- function createTemplate() {
- Δtemplate(
- 0, embeddedTemplate, 1, 1, 'ng-template', null, ['tplRef', ''], ΔtemplateRefExtractor);
- Δelement(2, 'p', ['vcref', '']);
- }
-
- function updateTemplate() {
- const tplRef = Δreference(1);
- ΔelementProperty(2, 'tplRef', Δbind(tplRef));
- }
describe('createEmbeddedView (incl. insert)', () => {
- it('should work on elements', () => {
- /**
- * {{name}}
- *
- *
- */
- function createTemplate() {
- Δtemplate(
- 0, embeddedTemplate, 1, 1, 'ng-template', null, ['tplRef', ''],
- ΔtemplateRefExtractor);
- Δelement(2, 'header', ['vcref', '']);
- Δelement(3, 'footer');
- }
-
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 4, 1, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
-
- createView('A');
- fixture.update();
- expect(fixture.html).toEqual('A');
-
- createView('B');
- createView('C');
- fixture.update();
- expect(fixture.html).toEqual('ABC');
-
- createView('Y', 0);
- fixture.update();
- expect(fixture.html).toEqual('YABC');
-
- expect(() => { createView('Z', -1); }).toThrow();
- expect(() => { createView('Z', 5); }).toThrow();
- });
-
- it('should work on components', () => {
- const HeaderComponent =
- createComponent('header-cmp', function(rf: RenderFlags, ctx: any) {});
-
- /**
- * {{name}}
- *
- *
- */
- function createTemplate() {
- Δtemplate(
- 0, embeddedTemplate, 1, 1, 'ng-template', [], ['tplRef', ''], ΔtemplateRefExtractor);
- Δelement(2, 'header-cmp', ['vcref', '']);
- Δelement(3, 'footer');
- }
-
- const fixture = new TemplateFixture(
- createTemplate, updateTemplate, 4, 1, [HeaderComponent, DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
-
- createView('A');
- fixture.update();
- expect(fixture.html).toEqual('A');
-
- createView('B');
- createView('C');
- fixture.update();
- expect(fixture.html).toEqual('ABC');
-
- createView('Y', 0);
- fixture.update();
- expect(fixture.html).toEqual('YABC');
-
- expect(() => { createView('Z', -1); }).toThrow();
- expect(() => { createView('Z', 5); }).toThrow();
- });
-
- it('should work with multiple instances with vcrefs', () => {
- let firstDir: DirectiveWithVCRef;
- let secondDir: DirectiveWithVCRef;
-
- /**
- * {{name}}
- *
- *
- */
- function createTemplate() {
- Δtemplate(
- 0, embeddedTemplate, 1, 1, 'ng-template', null, ['tplRef', ''],
- ΔtemplateRefExtractor);
- Δelement(2, 'div', ['vcref', '']);
- Δelement(3, 'div', ['vcref', '']);
-
- // for testing only:
- firstDir = getDirectiveOnNode(2);
- secondDir = getDirectiveOnNode(3);
- }
-
- function update() {
- const tplRef = Δreference(1);
- ΔelementProperty(2, 'tplRef', Δbind(tplRef));
- ΔelementProperty(3, 'tplRef', Δbind(tplRef));
- }
-
- const fixture = new TemplateFixture(createTemplate, update, 4, 2, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
-
- firstDir !.vcref.createEmbeddedView(firstDir !.tplRef, {name: 'A'});
- secondDir !.vcref.createEmbeddedView(secondDir !.tplRef, {name: 'B'});
- fixture.update();
- expect(fixture.html).toEqual('AB');
- });
-
- it('should work on templates', () => {
- /**
- * {{name}}
- *
- */
- function createTemplate() {
- Δtemplate(
- 0, embeddedTemplate, 1, 1, 'ng-template', ['vcref', ''], ['tplRef', ''],
- ΔtemplateRefExtractor);
- Δelement(2, 'footer');
- }
-
- function updateTemplate() {
- const tplRef = Δreference(1);
- ΔelementProperty(0, 'tplRef', Δbind(tplRef));
- }
-
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
-
- createView('A');
- fixture.update();
- expect(fixture.html).toEqual('A');
-
- createView('B');
- createView('C');
- fixture.update();
- expect(fixture.html).toEqual('ABC');
-
- createView('Y', 0);
- fixture.update();
- expect(fixture.html).toEqual('YABC');
-
- expect(() => { createView('Z', -1); }).toThrow();
- expect(() => { createView('Z', 5); }).toThrow();
- });
it('should add embedded views at the right position in the DOM tree (ng-template next to other ng-template)',
() => {
@@ -411,646 +228,11 @@ describe('ViewContainerRef', () => {
expect(fixture.html).toEqual('before|AAB|after');
});
- it('should apply directives and pipes of the host view to the TemplateRef', () => {
- @Component({selector: 'child', template: `{{name}}`})
- class Child {
- // TODO(issue/24571): remove '!'.
- name !: string;
-
- static ngComponentDef = ΔdefineComponent({
- type: Child,
- encapsulation: ViewEncapsulation.None,
- selectors: [['child']],
- factory: () => new Child(),
- consts: 1,
- vars: 1,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(0, Δinterpolation1('', cmp.name, ''));
- }
- },
- inputs: {name: 'name'}
- });
- }
-
- @Pipe({name: 'starPipe'})
- class StarPipe implements PipeTransform {
- transform(value: any) { return `**${value}**`; }
-
- static ngPipeDef = ΔdefinePipe({
- name: 'starPipe',
- type: StarPipe,
- factory: function StarPipe_Factory() { return new StarPipe(); },
- });
- }
-
- function SomeComponent_Template_0(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'child');
- Δpipe(1, 'starPipe');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'name', Δbind(ΔpipeBind1(1, 1, 'C')));
- }
- }
-
- @Component({
- template: `
-
-
-
-
-
- `
- })
- class SomeComponent {
- static ngComponentDef = ΔdefineComponent({
- type: SomeComponent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['some-comp']],
- factory: () => new SomeComponent(),
- consts: 6,
- vars: 7,
- template: (rf: RenderFlags, cmp: SomeComponent) => {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, SomeComponent_Template_0, 2, 3, 'ng-template', [], ['foo', ''],
- ΔtemplateRefExtractor);
- Δpipe(2, 'starPipe');
- Δelement(3, 'child', ['vcref', '']);
- Δpipe(4, 'starPipe');
- Δelement(5, 'child');
- }
- if (rf & RenderFlags.Update) {
- const tplRef = Δreference(1);
- ΔelementProperty(3, 'tplRef', Δbind(tplRef));
- ΔelementProperty(3, 'name', Δbind(ΔpipeBind1(2, 3, 'A')));
- ΔelementProperty(5, 'name', Δbind(ΔpipeBind1(4, 5, 'B')));
- }
- },
- directives: [Child, DirectiveWithVCRef],
- pipes: [StarPipe]
- });
- }
-
- const fixture = new ComponentFixture(SomeComponent);
- directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
- directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '**A****C****C****B**');
- });
- });
-
- describe('insertion points and declaration points', () => {
- class InsertionDir {
- // @Input()
- set tplDir(tpl: TemplateRef|null) {
- tpl ? this.vcr.createEmbeddedView(tpl) : this.vcr.clear();
- }
-
- constructor(public vcr: ViewContainerRef) {}
-
- static ngDirectiveDef = ΔdefineDirective({
- type: InsertionDir,
- selectors: [['', 'tplDir', '']],
- factory: () => new InsertionDir(ΔdirectiveInject(ViewContainerRef as any)),
- inputs: {tplDir: 'tplDir'}
- });
- }
-
- // see running stackblitz example: https://stackblitz.com/edit/angular-w3myy6
- it('should work with a template declared in a different component view from insertion',
- () => {
- let child: Child|null = null;
-
- /**
- * {{ name }}
- * // template insertion point
- */
- class Child {
- name = 'Child';
- tpl: TemplateRef|null = null;
-
- static ngComponentDef = ΔdefineComponent({
- type: Child,
- encapsulation: ViewEncapsulation.None,
- selectors: [['child']],
- factory: () => child = new Child(),
- consts: 2,
- vars: 2,
- template: function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'div', [AttributeMarker.Bindings, 'tplDir']);
- { Δtext(1); }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'tplDir', Δbind(ctx.tpl));
- ΔtextBinding(1, Δbind(ctx.name));
- }
- },
- inputs: {tpl: 'tpl'},
- directives: () => [InsertionDir]
- });
- }
-
- /**
- * // template declaration point
- *
- * {{ name }}
- *
- *
- * <-- template insertion inside
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, fooTemplate, 2, 1, 'ng-template', null, ['foo', ''], ΔtemplateRefExtractor);
- Δelement(2, 'child');
- }
-
- if (rf & RenderFlags.Update) {
- const tplRef = Δreference(1);
- ΔelementProperty(2, 'tpl', Δbind(tplRef));
- }
-
- }, 3, 1, [Child]);
-
- function fooTemplate(rf1: RenderFlags, ctx: any) {
- if (rf1 & RenderFlags.Create) {
- ΔelementStart(0, 'div');
- { Δtext(1); }
- ΔelementEnd();
- }
- if (rf1 & RenderFlags.Update) {
- const parent = ΔnextContext();
- ΔtextBinding(1, Δbind(parent.name));
- }
- }
-
- const fixture = new ComponentFixture(Parent);
- fixture.component.name = 'Parent';
- fixture.update();
-
- // Context should be inherited from the declaration point, not the insertion point,
- // so the template should read 'Parent'.
- expect(fixture.html).toEqual(`Child
Parent
`);
-
- child !.tpl = null;
- fixture.update();
- expect(fixture.html).toEqual(`Child
`);
- });
-
- // see running stackblitz example: https://stackblitz.com/edit/angular-3vplec
- it('should work with nested for loops with different declaration / insertion points', () => {
- /**
- *
- * // insertion point for templates (both row and cell)
- *
- */
- class LoopComp {
- name = 'Loop';
-
- // @Input()
- tpl !: TemplateRef;
-
- // @Input()
- rows !: any[];
-
- static ngComponentDef = ΔdefineComponent({
- type: LoopComp,
- encapsulation: ViewEncapsulation.None,
- selectors: [['loop-comp']],
- factory: () => new LoopComp(),
- consts: 1,
- vars: 2,
- template: function(rf: RenderFlags, loop: any) {
- if (rf & RenderFlags.Create) {
- Δtemplate(0, null, 0, 0, 'ng-template', [AttributeMarker.Bindings, 'ngForOf']);
- }
-
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'ngForOf', Δbind(loop.rows));
- ΔelementProperty(0, 'ngForTemplate', Δbind(loop.tpl));
- }
- },
- inputs: {tpl: 'tpl', rows: 'rows'},
- directives: () => [NgForOf]
- });
- }
-
- /**
- * // row declaration point
- *
- *
- * // cell declaration point
- *
- * {{ cell }} - {{ row.value }} - {{ name }}
- *
- *
- * <-- cell insertion
- *
- *
- * <-- row insertion
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, rowTemplate, 3, 2, 'ng-template', null, ['rowTemplate', ''],
- ΔtemplateRefExtractor);
- Δelement(2, 'loop-comp');
- }
-
- if (rf & RenderFlags.Update) {
- const rowTemplateRef = Δreference(1);
- ΔelementProperty(2, 'tpl', Δbind(rowTemplateRef));
- ΔelementProperty(2, 'rows', Δbind(parent.rows));
- }
-
- }, 3, 2, [LoopComp]);
-
- function rowTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, cellTemplate, 2, 3, 'ng-template', null, ['cellTemplate', ''],
- ΔtemplateRefExtractor);
- Δelement(2, 'loop-comp');
- }
-
- if (rf & RenderFlags.Update) {
- const row = ctx.$implicit as any;
- const cellTemplateRef = Δreference(1);
- ΔelementProperty(2, 'tpl', Δbind(cellTemplateRef));
- ΔelementProperty(2, 'rows', Δbind(row.data));
- }
- }
-
- function cellTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'div');
- { Δtext(1); }
- ΔelementEnd();
- }
-
- if (rf & RenderFlags.Update) {
- const cell = ctx.$implicit as any;
- const row = ΔnextContext().$implicit as any;
- const parent = ΔnextContext();
- ΔtextBinding(1, Δinterpolation3('', cell, ' - ', row.value, ' - ', parent.name, ''));
- }
- }
-
- const fixture = new ComponentFixture(Parent);
- fixture.component.name = 'Parent';
- fixture.component.rows =
- [{data: ['1', '2'], value: 'one'}, {data: ['3', '4'], value: 'two'}];
- fixture.update();
-
- expect(fixture.html)
- .toEqual(
- '' +
- '1 - one - Parent
2 - one - Parent
' +
- '3 - two - Parent
4 - two - Parent
' +
- '');
-
- fixture.component.rows = [{data: ['5', '6'], value: 'three'}, {data: ['7'], value: 'four'}];
- fixture.component.name = 'New name!';
- fixture.update();
-
- expect(fixture.html)
- .toEqual(
- '' +
- '5 - three - New name!
6 - three - New name!
' +
- '7 - four - New name!
' +
- '');
- });
- });
-
- const rendererFactory = getRendererFactory2(document);
-
- describe('detach', () => {
- it('should detach the right embedded view when an index is specified', () => {
- const fixture = new TemplateFixture(
- createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef], null, null,
- rendererFactory);
- const viewA = createView('A');
- createView('B');
- createView('C');
- const viewD = createView('D');
- createView('E');
- fixture.update();
- expect(fixture.html).toEqual('ABCDE');
-
- directiveInstance !.vcref.detach(3);
- fixture.update();
- expect(fixture.html).toEqual('ABCE');
- expect(viewD.destroyed).toBeFalsy();
-
- directiveInstance !.vcref.detach(0);
- fixture.update();
- expect(fixture.html).toEqual('BCE');
- expect(viewA.destroyed).toBeFalsy();
-
- expect(() => { directiveInstance !.vcref.detach(-1); }).toThrow();
- expect(() => { directiveInstance !.vcref.detach(42); }).toThrow();
- expect(ngDevMode).toHaveProperties({rendererDestroyNode: 0});
- });
-
-
- it('should detach the last embedded view when no index is specified', () => {
- const fixture = new TemplateFixture(
- createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef], null, null,
- rendererFactory);
- createView('A');
- createView('B');
- createView('C');
- createView('D');
- const viewE = createView('E');
- fixture.update();
- expect(fixture.html).toEqual('ABCDE');
-
- directiveInstance !.vcref.detach();
- fixture.update();
- expect(fixture.html).toEqual('ABCD');
- expect(viewE.destroyed).toBeFalsy();
- expect(ngDevMode).toHaveProperties({rendererDestroyNode: 0});
- });
- });
-
- describe('remove', () => {
- it('should remove the right embedded view when an index is specified', () => {
- const fixture = new TemplateFixture(
- createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef], null, null,
- rendererFactory);
- const viewA = createView('A');
- createView('B');
- createView('C');
- const viewD = createView('D');
- createView('E');
- fixture.update();
- expect(fixture.html).toEqual('ABCDE');
-
- directiveInstance !.vcref.remove(3);
- fixture.update();
- expect(fixture.html).toEqual('ABCE');
- expect(viewD.destroyed).toBeTruthy();
-
- directiveInstance !.vcref.remove(0);
- fixture.update();
- expect(fixture.html).toEqual('BCE');
- expect(viewA.destroyed).toBeTruthy();
-
- expect(() => { directiveInstance !.vcref.remove(-1); }).toThrow();
- expect(() => { directiveInstance !.vcref.remove(42); }).toThrow();
- expect(ngDevMode).toHaveProperties({rendererDestroyNode: 2});
- });
-
- it('should remove the last embedded view when no index is specified', () => {
- const fixture = new TemplateFixture(
- createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef], null, null,
- rendererFactory);
- createView('A');
- createView('B');
- createView('C');
- createView('D');
- const viewE = createView('E');
- fixture.update();
- expect(fixture.html).toEqual('ABCDE');
-
- directiveInstance !.vcref.remove();
- fixture.update();
- expect(fixture.html).toEqual('ABCD');
- expect(viewE.destroyed).toBeTruthy();
- expect(ngDevMode).toHaveProperties({rendererDestroyNode: 1});
- });
-
- it('should throw when trying to insert a removed or destroyed view', () => {
- const fixture = new TemplateFixture(
- createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef], null, null,
- rendererFactory);
- const viewA = createView('A');
- const viewB = createView('B');
- fixture.update();
-
- directiveInstance !.vcref.remove();
- fixture.update();
- expect(() => directiveInstance !.vcref.insert(viewB)).toThrow();
-
- viewA.destroy();
- fixture.update();
- expect(() => directiveInstance !.vcref.insert(viewA)).toThrow();
- });
- });
-
- describe('length', () => {
- it('should return the number of embedded views', () => {
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- expect(directiveInstance !.vcref.length).toEqual(0);
-
- createView('A');
- createView('B');
- createView('C');
- fixture.update();
- expect(directiveInstance !.vcref.length).toEqual(3);
-
- directiveInstance !.vcref.detach(1);
- fixture.update();
- expect(directiveInstance !.vcref.length).toEqual(2);
-
- directiveInstance !.vcref.clear();
- fixture.update();
- expect(directiveInstance !.vcref.length).toEqual(0);
- });
- });
-
- describe('get and indexOf', () => {
- it('should retrieve a ViewRef from its index, and vice versa', () => {
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- createView('A');
- createView('B');
- createView('C');
- fixture.update();
-
- let viewRef = directiveInstance !.vcref.get(0);
- expect(directiveInstance !.vcref.indexOf(viewRef !)).toEqual(0);
-
- viewRef = directiveInstance !.vcref.get(1);
- expect(directiveInstance !.vcref.indexOf(viewRef !)).toEqual(1);
-
- viewRef = directiveInstance !.vcref.get(2);
- expect(directiveInstance !.vcref.indexOf(viewRef !)).toEqual(2);
- });
-
- it('should handle out of bounds cases', () => {
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- createView('A');
- fixture.update();
-
- expect(directiveInstance !.vcref.get(-1)).toBeNull();
- expect(directiveInstance !.vcref.get(42)).toBeNull();
-
- const viewRef = directiveInstance !.vcref.get(0);
- directiveInstance !.vcref.remove(0);
- expect(directiveInstance !.vcref.indexOf(viewRef !)).toEqual(-1);
- });
- });
-
- describe('move', () => {
- it('should move embedded views and associated DOM nodes without recreating them', () => {
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- createView('A');
- createView('B');
- createView('C');
- fixture.update();
- expect(fixture.html).toEqual('ABC');
-
- // The DOM is manually modified here to ensure that the text node is actually moved
- fixture.hostElement.childNodes[2].nodeValue = '**A**';
- expect(fixture.html).toEqual('**A**BC');
-
- let viewRef = directiveInstance !.vcref.get(0);
- directiveInstance !.vcref.move(viewRef !, 2);
- fixture.update();
- expect(fixture.html).toEqual('BC**A**');
-
- directiveInstance !.vcref.move(viewRef !, 0);
- fixture.update();
- expect(fixture.html).toEqual('**A**BC');
-
- directiveInstance !.vcref.move(viewRef !, 1);
- fixture.update();
- expect(fixture.html).toEqual('B**A**C');
-
- expect(() => { directiveInstance !.vcref.move(viewRef !, -1); }).toThrow();
- expect(() => { directiveInstance !.vcref.move(viewRef !, 42); }).toThrow();
- });
});
describe('createComponent', () => {
let templateExecutionCounter = 0;
- it('should work without Injector and NgModuleRef', () => {
- class EmbeddedComponent {
- constructor() {}
-
- static ngComponentDef = ΔdefineComponent({
- type: EmbeddedComponent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['embedded-cmp']],
- factory: () => new EmbeddedComponent(),
- consts: 1,
- vars: 0,
- template: (rf: RenderFlags, cmp: EmbeddedComponent) => {
- templateExecutionCounter++;
- if (rf & RenderFlags.Create) {
- Δtext(0, 'foo');
- }
- }
- });
- }
-
- templateExecutionCounter = 0;
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
- expect(templateExecutionCounter).toEqual(0);
-
- const componentRef = directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent));
- fixture.update();
- expect(fixture.html).toEqual('foo');
- expect(templateExecutionCounter).toEqual(2);
-
- directiveInstance !.vcref.detach(0);
- fixture.update();
- expect(fixture.html).toEqual('');
- expect(templateExecutionCounter).toEqual(2);
-
- directiveInstance !.vcref.insert(componentRef.hostView);
- fixture.update();
- expect(fixture.html).toEqual('foo');
- expect(templateExecutionCounter).toEqual(3);
- });
-
- it('should work with NgModuleRef and Injector', () => {
- class EmbeddedComponent {
- constructor(public s: String) {}
-
- static ngComponentDef = ΔdefineComponent({
- type: EmbeddedComponent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['embedded-cmp']],
- factory: () => new EmbeddedComponent(ΔdirectiveInject(String)),
- consts: 1,
- vars: 0,
- template: (rf: RenderFlags, cmp: EmbeddedComponent) => {
- templateExecutionCounter++;
- if (rf & RenderFlags.Create) {
- Δtext(0, 'foo');
- }
- }
- });
- }
-
- class MyAppModule {
- static ngInjectorDef = ΔdefineInjector({
- factory: () => new MyAppModule(),
- imports: [],
- providers: [
- {provide: APP_ROOT, useValue: true},
- {provide: RendererFactory2, useValue: getRendererFactory2(document)},
- {provide: String, useValue: 'module'}
- ]
- });
- static ngModuleDef: NgModuleDef = { bootstrap: [] } as any;
- }
- const myAppModuleFactory = new NgModuleFactory(MyAppModule);
- const ngModuleRef = myAppModuleFactory.create(null);
-
- class SomeModule {
- static ngInjectorDef = ΔdefineInjector({
- factory: () => new SomeModule(),
- providers: [
- {provide: NgModuleRef, useValue: ngModuleRef},
- {provide: String, useValue: 'injector'}
- ]
- });
- }
- const injector = createInjector(SomeModule);
-
- templateExecutionCounter = 0;
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
- expect(templateExecutionCounter).toEqual(0);
-
- const componentRef = directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, injector);
- fixture.update();
- expect(fixture.html).toEqual('foo');
- expect(templateExecutionCounter).toEqual(2);
- expect(componentRef.instance.s).toEqual('injector');
-
- directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, undefined,
- undefined, ngModuleRef);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'foofoo');
- expect(templateExecutionCounter).toEqual(5);
- });
-
describe('ComponentRef', () => {
let dynamicComp !: DynamicComp;
@@ -1129,101 +311,6 @@ describe('ViewContainerRef', () => {
});
});
-
- class EmbeddedComponentWithNgContent {
- static ngComponentDef = ΔdefineComponent({
- type: EmbeddedComponentWithNgContent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['embedded-cmp-with-ngcontent']],
- factory: () => new EmbeddedComponentWithNgContent(),
- consts: 3,
- vars: 0,
- template: (rf: RenderFlags, cmp: EmbeddedComponentWithNgContent) => {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef();
- Δprojection(0, 0);
- Δelement(1, 'hr');
- Δprojection(2, 1);
- }
- }
- });
- }
-
- it('should support projectable nodes', () => {
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
-
- const myNode = document.createElement('div');
- const myText = document.createTextNode('bar');
- const myText2 = document.createTextNode('baz');
- myNode.appendChild(myText);
- myNode.appendChild(myText2);
-
- directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponentWithNgContent), 0,
- undefined, [[myNode]]);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'barbaz
');
- });
-
- it('should support reprojection of projectable nodes', () => {
- class Reprojector {
- static ngComponentDef = ΔdefineComponent({
- type: Reprojector,
- encapsulation: ViewEncapsulation.None,
- selectors: [['reprojector']],
- factory: () => new Reprojector(),
- consts: 2,
- vars: 0,
- template: (rf: RenderFlags, cmp: Reprojector) => {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef();
- ΔelementStart(0, 'embedded-cmp-with-ngcontent');
- { Δprojection(1, 0); }
- ΔelementEnd();
- }
- },
- directives: [EmbeddedComponentWithNgContent]
- });
- }
-
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
-
- const myNode = document.createElement('div');
- const myText = document.createTextNode('bar');
- const myText2 = document.createTextNode('baz');
- myNode.appendChild(myText);
- myNode.appendChild(myText2);
-
- directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(Reprojector), 0, undefined, [[myNode]]);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'barbaz
');
- });
-
- it('should support many projectable nodes with many slots', () => {
- const fixture =
- new TemplateFixture(createTemplate, updateTemplate, 3, 1, [DirectiveWithVCRef]);
- expect(fixture.html).toEqual('');
-
- directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponentWithNgContent), 0,
- undefined, [
- [document.createTextNode('1'), document.createTextNode('2')],
- [document.createTextNode('3'), document.createTextNode('4')]
- ]);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '12
34');
- });
});
describe('getters', () => {
@@ -1261,657 +348,9 @@ describe('ViewContainerRef', () => {
.toEqual('header-cmp');
expect(() => directiveInstance !.vcref.parentInjector.get(ElementRef)).toThrow();
});
-
- it('should work on templates', () => {
- function createTemplate() {
- Δtemplate(0, embeddedTemplate, 1, 1, 'ng-template', ['vcref', '']);
- Δelement(1, 'footer');
- }
-
- new TemplateFixture(createTemplate, () => {}, 2, 0, [DirectiveWithVCRef]);
- expect(directiveInstance !.vcref.element.nativeElement.textContent).toEqual('container');
- expect(directiveInstance !.vcref.injector.get(ElementRef).nativeElement.textContent)
- .toEqual('container');
- expect(() => directiveInstance !.vcref.parentInjector.get(ElementRef)).toThrow();
- });
});
});
- describe('projection', () => {
- function embeddedTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'span');
- Δtext(1);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(1, ctx.name);
- }
- }
-
- it('should project the ViewContainerRef content along its host, in an element', () => {
- @Component({selector: 'child', template: '
'})
- class Child {
- static ngComponentDef = ΔdefineComponent({
- type: Child,
- encapsulation: ViewEncapsulation.None,
- selectors: [['child']],
- factory: () => new Child(),
- consts: 2,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef();
- ΔelementStart(0, 'div');
- { Δprojection(1); }
- ΔelementEnd();
- }
- }
- });
- }
-
- @Component({
- selector: 'parent',
- template: `
-
- {{name}}
-
- `
- })
- class Parent {
- name: string = 'bar';
- static ngComponentDef = ΔdefineComponent({
- type: Parent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['parent']],
- factory: () => new Parent(),
- consts: 5,
- vars: 2,
- template: (rf: RenderFlags, cmp: Parent) => {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, embeddedTemplate, 2, 1, 'ng-template', null, ['foo', ''],
- ΔtemplateRefExtractor);
- ΔelementStart(2, 'child');
- {
- ΔelementStart(3, 'header', ['vcref', '']);
- { Δtext(4, 'blah'); }
- ΔelementEnd();
- }
- ΔelementEnd();
- }
- let tplRef: any;
- if (rf & RenderFlags.Update) {
- tplRef = Δreference(1);
- ΔelementProperty(3, 'tplRef', Δbind(tplRef));
- ΔelementProperty(3, 'name', Δbind(cmp.name));
- }
- },
- directives: [Child, DirectiveWithVCRef]
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html).toEqual('');
-
- directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
- fixture.update();
- expect(fixture.html)
- .toEqual('bar
');
- });
-
- it('should project the ViewContainerRef content along its host, in a view', () => {
- @Component({
- selector: 'child-with-view',
- template: `
- Before (inside)-
- % if (show) {
-
- % }
- After (inside)
- `
- })
- class ChildWithView {
- show: boolean = true;
- static ngComponentDef = ΔdefineComponent({
- type: ChildWithView,
- encapsulation: ViewEncapsulation.None,
- selectors: [['child-with-view']],
- factory: () => new ChildWithView(),
- consts: 3,
- vars: 0,
- template: (rf: RenderFlags, cmp: ChildWithView) => {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef();
- Δtext(0, 'Before (inside)-');
- Δcontainer(1);
- Δtext(2, 'After (inside)');
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(1);
- if (cmp.show) {
- let rf0 = ΔembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- Δprojection(0);
- }
- ΔembeddedViewEnd();
- }
- ΔcontainerRefreshEnd();
- }
- }
- });
- }
-
- @Component({
- selector: 'parent',
- template: `
-
- {{name}}
-
-
- Before projected
-
- After projected
- `
- })
- class Parent {
- name: string = 'bar';
- static ngComponentDef = ΔdefineComponent({
- type: Parent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['parent']],
- factory: () => new Parent(),
- consts: 7,
- vars: 2,
- template: (rf: RenderFlags, cmp: Parent) => {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, embeddedTemplate, 2, 1, 'ng-template', undefined, ['foo', ''],
- ΔtemplateRefExtractor);
- ΔelementStart(2, 'child-with-view');
- Δtext(3, 'Before projected');
- ΔelementStart(4, 'header', ['vcref', '']);
- Δtext(5, 'blah');
- ΔelementEnd();
- Δtext(6, 'After projected-');
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- const tplRef = Δreference(1);
- ΔelementProperty(4, 'tplRef', Δbind(tplRef));
- ΔelementProperty(4, 'name', Δbind(cmp.name));
- }
- },
- directives: [ChildWithView, DirectiveWithVCRef]
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual(
- 'Before (inside)-Before projectedAfter projected-After (inside)');
-
- directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'Before (inside)-Before projectedbarAfter projected-After (inside)');
- });
-
- describe('with select', () => {
- @Component({
- selector: 'child-with-selector',
- template: `
-
- `
- })
- class ChildWithSelector {
- static ngComponentDef = ΔdefineComponent({
- type: ChildWithSelector,
- encapsulation: ViewEncapsulation.None,
- selectors: [['child-with-selector']],
- factory: () => new ChildWithSelector(),
- consts: 4,
- vars: 0,
- template: (rf: RenderFlags, cmp: ChildWithSelector) => {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['header']]]);
- ΔelementStart(0, 'first');
- { Δprojection(1, 1); }
- ΔelementEnd();
- ΔelementStart(2, 'second');
- { Δprojection(3); }
- ΔelementEnd();
- }
- },
- directives: [ChildWithSelector, DirectiveWithVCRef]
- });
- }
-
- it('should project the ViewContainerRef content along its host, when the host matches a selector',
- () => {
- @Component({
- selector: 'parent',
- template: `
-
- {{name}}
-
- `
- })
- class Parent {
- name: string = 'bar';
- static ngComponentDef = ΔdefineComponent({
- type: Parent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['parent']],
- factory: () => new Parent(),
- consts: 5,
- vars: 2,
- template: (rf: RenderFlags, cmp: Parent) => {
- let tplRef: any;
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, embeddedTemplate, 2, 1, 'ng-template', null, ['foo', ''],
- ΔtemplateRefExtractor);
- ΔelementStart(2, 'child-with-selector');
- ΔelementStart(3, 'header', ['vcref', '']);
- Δtext(4, 'blah');
- ΔelementEnd();
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- tplRef = Δreference(1);
- ΔelementProperty(3, 'tplRef', Δbind(tplRef));
- ΔelementProperty(3, 'name', Δbind(cmp.name));
- }
- },
- directives: [ChildWithSelector, DirectiveWithVCRef]
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual(
- '');
-
- directiveInstance !.vcref.createEmbeddedView(
- directiveInstance !.tplRef, fixture.component);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'bar');
- });
-
- it('should not project the ViewContainerRef content, when the host does not match a selector',
- () => {
- @Component({
- selector: 'parent',
- template: `
-
- {{name}}
-
- `
- })
- class Parent {
- name: string = 'bar';
- static ngComponentDef = ΔdefineComponent({
- type: Parent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['parent']],
- factory: () => new Parent(),
- consts: 5,
- vars: 2,
- template: (rf: RenderFlags, cmp: Parent) => {
- let tplRef: any;
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, embeddedTemplate, 2, 1, 'ng-template', null, ['foo', ''],
- ΔtemplateRefExtractor);
- ΔelementStart(2, 'child-with-selector');
- ΔelementStart(3, 'footer', ['vcref', '']);
- Δtext(4, 'blah');
- ΔelementEnd();
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- tplRef = Δreference(1);
- ΔelementProperty(3, 'tplRef', Δbind(tplRef));
- ΔelementProperty(3, 'name', Δbind(cmp.name));
- }
- },
- directives: [ChildWithSelector, DirectiveWithVCRef]
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual(
- '');
-
- directiveInstance !.vcref.createEmbeddedView(
- directiveInstance !.tplRef, fixture.component);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'bar');
- });
- });
- });
-
- describe('life cycle hooks', () => {
-
- // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
- const log: string[] = [];
-
- @Component({selector: 'hooks', template: `{{name}}`})
- class ComponentWithHooks {
- // TODO(issue/24571): remove '!'.
- name !: string;
-
- private log(msg: string) { log.push(msg); }
-
- ngOnChanges() { this.log('onChanges-' + this.name); }
- ngOnInit() { this.log('onInit-' + this.name); }
- ngDoCheck() { this.log('doCheck-' + this.name); }
-
- ngAfterContentInit() { this.log('afterContentInit-' + this.name); }
- ngAfterContentChecked() { this.log('afterContentChecked-' + this.name); }
-
- ngAfterViewInit() { this.log('afterViewInit-' + this.name); }
- ngAfterViewChecked() { this.log('afterViewChecked-' + this.name); }
-
- ngOnDestroy() { this.log('onDestroy-' + this.name); }
-
- static ngComponentDef = ΔdefineComponent({
- type: ComponentWithHooks,
- encapsulation: ViewEncapsulation.None,
- selectors: [['hooks']],
- factory: () => new ComponentWithHooks(),
- consts: 1,
- vars: 1,
- template: (rf: RenderFlags, cmp: ComponentWithHooks) => {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(0, Δinterpolation1('', cmp.name, ''));
- }
- },
- features: [ΔNgOnChangesFeature()],
- inputs: {name: 'name'}
- });
- }
-
- it('should call all hooks in correct order when creating with createEmbeddedView', () => {
- function SomeComponent_Template_0(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'hooks');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'name', Δbind('C'));
- }
- }
-
- @Component({
- template: `
-
-
-
-
-
- `
- })
- class SomeComponent {
- static ngComponentDef = ΔdefineComponent({
- type: SomeComponent,
- selectors: [['some-comp']],
- factory: () => new SomeComponent(),
- consts: 4,
- vars: 3,
- template: (rf: RenderFlags, cmp: SomeComponent) => {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, SomeComponent_Template_0, 1, 1, 'ng-template', [], ['foo', ''],
- ΔtemplateRefExtractor);
- Δelement(2, 'hooks', ['vcref', '']);
- Δelement(3, 'hooks');
- }
- if (rf & RenderFlags.Update) {
- const tplRef = Δreference(1);
- ΔelementProperty(2, 'tplRef', Δbind(tplRef));
- ΔelementProperty(2, 'name', Δbind('A'));
- ΔelementProperty(3, 'name', Δbind('B'));
- }
- },
- directives: [ComponentWithHooks, DirectiveWithVCRef],
- features: [ΔNgOnChangesFeature()],
- });
- }
-
- log.length = 0;
-
- const fixture = new ComponentFixture(SomeComponent);
- expect(log).toEqual([
- 'onChanges-A', 'onInit-A', 'doCheck-A', 'onChanges-B', 'onInit-B', 'doCheck-B',
- 'afterContentInit-A', 'afterContentChecked-A', 'afterContentInit-B',
- 'afterContentChecked-B', 'afterViewInit-A', 'afterViewChecked-A', 'afterViewInit-B',
- 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
- 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
- expect(fixture.html).toEqual('AB');
- expect(log).toEqual([]);
-
- log.length = 0;
- fixture.update();
- expect(fixture.html).toEqual('ACB');
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'onChanges-C', 'onInit-C', 'doCheck-C', 'afterContentInit-C',
- 'afterContentChecked-C', 'afterViewInit-C', 'afterViewChecked-C', 'afterContentChecked-A',
- 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'doCheck-C', 'afterContentChecked-C', 'afterViewChecked-C',
- 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- const viewRef = directiveInstance !.vcref.detach(0);
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
- 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- directiveInstance !.vcref.insert(viewRef !);
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'doCheck-C', 'afterContentChecked-C', 'afterViewChecked-C',
- 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- directiveInstance !.vcref.remove(0);
- fixture.update();
- expect(log).toEqual([
- 'onDestroy-C', 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
- 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
- });
-
- it('should call all hooks in correct order when creating with createComponent', () => {
- @Component({
- template: `
-
-
- `
- })
- class SomeComponent {
- static ngComponentDef = ΔdefineComponent({
- type: SomeComponent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['some-comp']],
- factory: () => new SomeComponent(),
- consts: 2,
- vars: 2,
- template: (rf: RenderFlags, cmp: SomeComponent) => {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'hooks', ['vcref', '']);
- Δelement(1, 'hooks');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'name', Δbind('A'));
- ΔelementProperty(1, 'name', Δbind('B'));
- }
- },
- directives: [ComponentWithHooks, DirectiveWithVCRef],
- features: [ΔNgOnChangesFeature()],
- });
- }
-
- log.length = 0;
-
- const fixture = new ComponentFixture(SomeComponent);
- expect(log).toEqual([
- 'onChanges-A', 'onInit-A', 'doCheck-A', 'onChanges-B', 'onInit-B', 'doCheck-B',
- 'afterContentInit-A', 'afterContentChecked-A', 'afterContentInit-B',
- 'afterContentChecked-B', 'afterViewInit-A', 'afterViewChecked-A', 'afterViewInit-B',
- 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
- 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- const componentRef = directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(ComponentWithHooks));
- expect(fixture.html).toEqual('AB');
- expect(log).toEqual([]);
-
- componentRef.instance.name = 'D';
- log.length = 0;
- fixture.update();
- expect(fixture.html).toEqual('ADB');
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'onInit-D', 'doCheck-D', 'afterContentInit-D',
- 'afterContentChecked-D', 'afterViewInit-D', 'afterViewChecked-D', 'afterContentChecked-A',
- 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'doCheck-D', 'afterContentChecked-D', 'afterViewChecked-D',
- 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- const viewRef = directiveInstance !.vcref.detach(0);
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
- 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- directiveInstance !.vcref.insert(viewRef !);
- fixture.update();
- expect(log).toEqual([
- 'doCheck-A', 'doCheck-B', 'doCheck-D', 'afterContentChecked-D', 'afterViewChecked-D',
- 'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
-
- log.length = 0;
- directiveInstance !.vcref.remove(0);
- fixture.update();
- expect(log).toEqual([
- 'onDestroy-D', 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
- 'afterViewChecked-A', 'afterViewChecked-B'
- ]);
- });
- });
-
- describe('host bindings', () => {
-
- it('should support host bindings on dynamically created components', () => {
-
- @Component(
- {selector: 'host-bindings', host: {'id': 'attribute', '[title]': 'title'}, template: ``})
- class HostBindingCmpt {
- title = 'initial';
-
- static ngComponentDef = ΔdefineComponent({
- type: HostBindingCmpt,
- selectors: [['host-bindings']],
- factory: () => new HostBindingCmpt(),
- consts: 0,
- vars: 0,
- template: (rf: RenderFlags, cmp: HostBindingCmpt) => {},
- hostBindings: function(rf: RenderFlags, ctx: HostBindingCmpt, elIndex: number) {
- if (rf & RenderFlags.Create) {
- ΔelementHostAttrs(['id', 'attribute']);
- ΔallocHostVars(1);
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(elIndex, 'title', Δbind(ctx.title));
- }
- },
- });
- }
-
- @Component({
- template: `
-
- `
- })
- class AppCmpt {
- static ngComponentDef = ΔdefineComponent({
- type: AppCmpt,
- selectors: [['app']],
- factory: () => new AppCmpt(),
- consts: 1,
- vars: 0,
- template: (rf: RenderFlags, cmp: AppCmpt) => {
- if (rf & RenderFlags.Create) {
- Δtemplate(0, null, 0, 0, 'ng-template', ['vcref', '']);
- }
- },
- directives: [HostBindingCmpt, DirectiveWithVCRef]
- });
- }
-
- const fixture = new ComponentFixture(AppCmpt);
- expect(fixture.html).toBe('');
-
- const componentRef = directiveInstance !.vcref.createComponent(
- directiveInstance !.cfr.resolveComponentFactory(HostBindingCmpt));
- fixture.update();
- expect(fixture.html).toBe('');
-
-
- componentRef.instance.title = 'changed';
- fixture.update();
- expect(fixture.html).toBe('');
- });
-
- });
-
describe('view engine compatibility', () => {
@Component({selector: 'app', template: ''})
@@ -1977,79 +416,6 @@ describe('ViewContainerRef', () => {
expect(parentInjector.get('foo')).toEqual('bar');
});
- it('should check bindings for components dynamically created by root component', () => {
- class DynamicCompWithBindings {
- checkCount = 0;
-
- ngDoCheck() { this.checkCount++; }
-
- /** check count: {{ checkCount }} */
- static ngComponentDef = ΔdefineComponent({
- type: DynamicCompWithBindings,
- selectors: [['dynamic-cmpt-with-bindings']],
- factory: () => new DynamicCompWithBindings(),
- consts: 1,
- vars: 1,
- template: (rf: RenderFlags, ctx: DynamicCompWithBindings) => {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(0, Δinterpolation1('check count: ', ctx.checkCount, ''));
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(AppCmpt);
- expect(fixture.outerHtml).toBe('');
-
- fixture.component.insert(DynamicCompWithBindings);
- fixture.update();
- expect(fixture.outerHtml)
- .toBe(
- 'check count: 1');
-
- fixture.update();
- expect(fixture.outerHtml)
- .toBe(
- 'check count: 2');
- });
-
- it('should create deep DOM tree immediately for dynamically created components', () => {
- let name = 'text';
- const Child = createComponent('child', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'div');
- { Δtext(1); }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(1, Δbind(name));
- }
- }, 2, 1);
-
- const DynamicCompWithChildren =
- createComponent('dynamic-cmpt-with-children', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'child');
- }
- }, 1, 0, [Child]);
-
- const fixture = new ComponentFixture(AppCmpt);
- expect(fixture.outerHtml).toBe('');
-
- fixture.component.insert(DynamicCompWithChildren);
- expect(fixture.outerHtml)
- .toBe(
- '');
-
- fixture.update();
- expect(fixture.outerHtml)
- .toBe(
- 'text
');
- });
-
it('should support view queries for dynamically created components', () => {
let dynamicComp !: DynamicCompWithViewQueries;
let fooEl !: RElement;