diff --git a/packages/core/test/acceptance/content_spec.ts b/packages/core/test/acceptance/content_spec.ts
index d0915c9538..24ca05122f 100644
--- a/packages/core/test/acceptance/content_spec.ts
+++ b/packages/core/test/acceptance/content_spec.ts
@@ -155,6 +155,84 @@ describe('projection', () => {
`
Some content
Other content `);
});
+ it('should project with multiple instances of a component with projection', () => {
+ @Component({selector: 'child', template: `
`})
+ class Child {
+ }
+
+ @Component({selector: 'projected-comp', template: `Before After`})
+ class ProjectedComp {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+ A
123
+ B
456
+ `,
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child, ProjectedComp]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML)
+ .toBe(
+ '' +
+ '
BeforeA
123
After ' +
+ '
BeforeB
456
After ' +
+ '
');
+ });
+
+ it('should re-project with multiple instances of a component with projection', () => {
+ @Component({selector: 'child', template: `
`})
+ class Child {
+ }
+
+ @Component({selector: 'projected-comp', template: `Before After`})
+ class ProjectedComp {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+ A
123
+ B
456
+ `,
+ })
+ class Parent {
+ }
+
+ @Component({
+ selector: 'app',
+ template: `
+ **ABC**
+ **DEF**
+ `,
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, Parent, Child, ProjectedComp]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML)
+ .toBe(
+ '' +
+ '
BeforeA
**ABC**123
After ' +
+ '
BeforeB
456
After ' +
+ '
' +
+ '' +
+ '
BeforeA
**DEF**123
After ' +
+ '
BeforeB
456
After ' +
+ '
');
+ });
+
it('should project into dynamic views (with createEmbeddedView)', () => {
@Component({
selector: 'child',
@@ -280,6 +358,27 @@ describe('projection', () => {
.toBe(` Some content `);
});
+ it('should project nodes into the last ng-content', () => {
+ @Component({
+ selector: 'child',
+ template: `
+ `
+ })
+ class Child {
+ }
+
+ @Component({selector: 'parent', template: `content `})
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toBe('
content ');
+ });
+
// https://stackblitz.com/edit/angular-ceqmnw?file=src%2Fapp%2Fapp.component.ts
it('should project nodes into the last ng-content unrolled by ngFor', () => {
@Component({
@@ -408,6 +507,64 @@ describe('projection', () => {
expect(fixture.nativeElement.querySelectorAll('div').length).toBe(0);
});
+ it('should project ng-container at the content root', () => {
+ @Component({selector: 'child', template: ` `})
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+ content
+
+
+ `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child]});
+ const fixture = TestBed.createComponent(Parent);
+
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement)).toBe('content ');
+ });
+
+ it('should re-project ng-container at the content root', () => {
+ @Component({selector: 'grand-child', template: ` `})
+ class GrandChild {
+ }
+
+ @Component({
+ selector: 'child',
+ template: `
+
+ `
+ })
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+ content
+
+
+ `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
+ const fixture = TestBed.createComponent(Parent);
+
+ fixture.detectChanges();
+ expect(getElementHtml(fixture.nativeElement))
+ .toBe('content ');
+ });
+
it('should handle re-projection at the root of an embedded view', () => {
@Component({
selector: 'child-comp',
@@ -442,6 +599,246 @@ describe('projection', () => {
});
describe('with selectors', () => {
+ it('should project nodes using attribute selectors', () => {
+ @Component({
+ selector: 'child',
+ template: `
+
`,
+ })
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `1 2 `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '1
2
');
+ });
+
+ it('should project nodes using class selectors', () => {
+ @Component({
+ selector: 'child',
+ template: `
+
`,
+ })
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `1 2 `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '1
2
');
+ });
+
+ it('should project nodes using class selectors when element has multiple classes', () => {
+ @Component({
+ selector: 'child',
+ template: `
+
`
+ })
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template:
+ `1 2 `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '1
2
');
+ });
+
+ it('should project nodes into the first matching selector', () => {
+ @Component({
+ selector: 'child',
+ template: `
+
`
+ })
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `1 2 `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '1 2
');
+ });
+
+ it('should allow mixing ng-content with and without selectors', () => {
+ @Component({
+ selector: 'child',
+ template: `
+
`
+ })
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template:
+ `1 remaining more remaining `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '1
remaining more remaining
');
+ });
+
+ it('should allow mixing ng-content with and without selectors - ng-content first', () => {
+ @Component({
+ selector: 'child',
+ template: `
+
`
+ })
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `1 2 remaining `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ '1 remaining
2
');
+ });
+
+ /**
+ * Descending into projected content for selector-matching purposes is not supported
+ * today: http://plnkr.co/edit/MYQcNfHSTKp9KvbzJWVQ?p=preview
+ */
+ it('should not descend into re-projected content', () => {
+ @Component({
+ selector: 'grand-child',
+ template: ` `
+ })
+ class GrandChild {
+ }
+
+ @Component({
+ selector: 'child',
+ template: `
+
+ in child template
+ `
+ })
+ class Child {
+ }
+
+ @Component({selector: 'parent', template: `parent content `})
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [GrandChild, Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'in child template parent content ');
+ });
+
+ it('should not descend into re-projected content', () => {
+ @Component({
+ selector: 'card',
+ template:
+ ` `
+ })
+ class Card {
+ }
+
+ @Component({
+ selector: 'card-with-title',
+ template: `
+ Title
+
+ `
+ })
+ class CardWithTitle {
+ }
+
+ @Component({selector: 'parent', template: `content `})
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Card, CardWithTitle, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual(
+ 'Title content ');
+ });
+
+ it('should not match selectors against node having ngProjectAs attribute', () => {
+ @Component({selector: 'child', template: ` `})
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template:
+ `should not project
should project
`
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Child, Parent]});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+
+ expect(getElementHtml(fixture.nativeElement))
+ .toEqual('should project
');
+ });
+
// https://stackblitz.com/edit/angular-psokum?file=src%2Fapp%2Fapp.module.ts
it('should project nodes where attribute selector matches a binding', () => {
@Component({
diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts
deleted file mode 100644
index c70a83e6f0..0000000000
--- a/packages/core/test/render3/content_spec.ts
+++ /dev/null
@@ -1,1345 +0,0 @@
-/**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-
-import {SelectorFlags} from '@angular/core/src/render3/interfaces/projection';
-
-import {AttributeMarker, detectChanges} from '../../src/render3/index';
-import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtext} from '../../src/render3/instructions/all';
-import {RenderFlags} from '../../src/render3/interfaces/definition';
-import {ComponentFixture, createComponent, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
-
-describe('content projection', () => {
- it('should project containers', () => {
- /**
*/
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }, 2);
-
- /**
- *
- * (
- * % if (value) {
- * content
- * % }
- * )
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵtext(1, '(');
- ɵɵcontainer(2);
- ɵɵtext(3, ')');
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(2);
- {
- if (ctx.value) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵtext(0, 'content');
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 4, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('()
');
- parent.value = true;
- detectChanges(parent);
-
- expect(toHtml(parent)).toEqual('(content)
');
- parent.value = false;
- detectChanges(parent);
-
- expect(toHtml(parent)).toEqual('()
');
- });
-
- it('should project containers into root', () => {
- /** */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵprojection(0);
- }
- }, 1);
-
- /**
- *
- * % if (value) {
- * content
- * % }
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- { ɵɵcontainer(1); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(1);
- {
- if (ctx.value) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵtext(0, 'content');
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 2, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual(' ');
-
- parent.value = true;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('content ');
-
- parent.value = false;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual(' ');
- });
-
- it('should project containers with if-else.', () => {
- /**
*/
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }, 2);
-
- /**
- *
- * (
- * % if (value) {
- * content
- * % } else {
- * else
- * % }
- * )
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵtext(1, '(');
- ɵɵcontainer(2);
- ɵɵtext(3, ')');
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(2);
- {
- if (ctx.value) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵtext(0, 'content');
- }
- ɵɵembeddedViewEnd();
- } else {
- if (ɵɵembeddedViewStart(1, 1, 0)) {
- ɵɵtext(0, 'else');
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 4, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('(else)
');
- parent.value = true;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('(content)
');
- parent.value = false;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('(else)
');
- });
-
- it('should support projection into embedded views', () => {
- let childCmptInstance: any;
-
- /**
- *
- * % if (!skipContent) {
- *
- *
- *
- * % }
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵcontainer(1); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(1);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 2, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵelementStart(0, 'span');
- ɵɵprojection(1);
- ɵɵelementEnd();
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 2, 0);
-
- /**
- *
- * text
- * content
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'div');
- { ɵɵtext(2, 'text'); }
- ɵɵelementEnd();
- ɵɵtext(3, 'content');
- }
- ɵɵelementEnd();
-
- // testing
- childCmptInstance = getDirectiveOnNode(0);
- }
- }, 4, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual(' ');
-
- childCmptInstance.skipContent = true;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('
');
- });
-
- it('should support projection into embedded views when no projected nodes', () => {
- let childCmptInstance: any;
-
- /**
- *
- * % if (!skipContent) {
- *
- * text
- * % }
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵcontainer(1); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(1);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 2, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵprojection(0);
- ɵɵtext(1, 'text');
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 2);
-
- /** */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'child');
-
- // testing
- childCmptInstance = getDirectiveOnNode(0);
- }
- }, 1, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('text
');
-
- childCmptInstance.skipContent = true;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('
');
- });
-
- it('should support projection into embedded views when ng-content is a root node of an embedded view',
- () => {
- let childCmptInstance: any;
-
- /**
- *
- * % if (!skipContent) {
- *
- * % }
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵcontainer(1); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(1);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵprojection(0);
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 2);
-
- /**
- * content
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- childCmptInstance = getDirectiveOnNode(0);
- ɵɵtext(1, 'content');
- }
- ɵɵelementEnd();
- }
- }, 2, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('content
');
-
- childCmptInstance.skipContent = true;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('
');
- });
-
- it('should project containers into containers', () => {
- /**
- *
- * Before (inside)
- * % if (!skipContent) {
- *
- * % }
- * After (inside)
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- {
- ɵɵtext(1, 'Before (inside)-');
- ɵɵcontainer(2);
- ɵɵtext(3, '-After (inside)');
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(2);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵprojection(0);
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 4);
-
- /**
- *
- * Before text-
- * % if (!skipContent) {
- * content
- * % }
- * -After text
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵtext(1, 'Before text-');
- ɵɵcontainer(2);
- ɵɵtext(3, '-After text');
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(2);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵtext(0, 'content');
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 4, 0, [Child]);
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual(
- 'Before (inside)-Before text-content-After text-After (inside)
');
-
- fixture.component.skipContent = true;
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'Before (inside)-Before text--After text-After (inside)
');
- });
-
- it('should re-project containers into containers', () => {
- /**
- *
- * % if (!skipContent) {
- *
- * % }
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵcontainer(1); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(1);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵprojection(0);
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 2);
-
- /**
- *
- * Before text
- * % if (!skipContent) {
- *
- * % }
- * -After text
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'child');
- {
- ɵɵtext(1, 'Before text');
- ɵɵcontainer(2);
- ɵɵtext(3, '-After text');
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(2);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵprojection(0);
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 4, 0, [Child]);
-
- let parent: any;
- /** text
*/
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'parent');
- {
- ɵɵelementStart(1, 'p');
- { ɵɵtext(2, 'text'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- // testing
- parent = getDirectiveOnNode(0);
- }
- }, 3, 0, [Parent]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html)
- .toEqual('Before text
text
-After text
');
-
- parent.skipContent = true;
- fixture.update();
- expect(fixture.html)
- .toEqual('Before text-After text
');
- });
-
- it('should support projection into embedded views when ng-content is a root node of an embedded view, with other nodes after',
- () => {
- let childCmptInstance: any;
-
- /**
- *
- * % if (!skipContent) {
- * before- -after
- * % }
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵcontainer(1); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(1);
- {
- if (!ctx.skipContent) {
- let rf0 = ɵɵembeddedViewStart(0, 3, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵtext(0, 'before-');
- ɵɵprojection(1);
- ɵɵtext(2, '-after');
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 2);
-
- /**
- * content
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- childCmptInstance = getDirectiveOnNode(0);
- ɵɵtext(1, 'content');
- }
- ɵɵelementEnd();
- }
- }, 2, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('before-content-after
');
-
- childCmptInstance.skipContent = true;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('
');
- });
-
- it('should project nodes into the last ng-content', () => {
- /**
- *
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'span');
- { ɵɵprojection(3); }
- ɵɵelementEnd();
- }
- }, 4);
-
- /**
- * content
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- { ɵɵtext(1, 'content'); }
- ɵɵelementEnd();
- }
- }, 2, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('
content ');
- });
-
- /**
- * Warning: this test is _not_ in-line with what Angular does atm.
- * Moreover the current implementation logic will result in DOM nodes
- * being re-assigned from one parent to another. Proposal: have compiler
- * to remove all but the latest occurrence of so we generate
- * only one P(n, m, 0) instruction. It would make it consistent with the
- * current Angular behavior:
- * http://plnkr.co/edit/OAYkNawTDPkYBFTqovTP?p=preview
- */
- it('should project nodes into the last available ng-content', () => {
- let childCmptInstance: any;
- /**
- *
- *
- * % if (show) {
- *
- * % }
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵprojection(0);
- ɵɵelementStart(1, 'div');
- { ɵɵcontainer(2); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(2);
- {
- if (ctx.show) {
- let rf0 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf0 & RenderFlags.Create) {
- ɵɵprojection(0);
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 3);
-
- /**
- * content
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- childCmptInstance = getDirectiveOnNode(0);
- ɵɵtext(1, 'content');
- }
- ɵɵelementEnd();
- }
- }, 2, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('content
');
-
- childCmptInstance.show = true;
- detectChanges(parent);
- expect(toHtml(parent)).toEqual('content
');
- });
-
- it('should project with multiple instances of a component with projection', () => {
- const ProjectionComp = createComponent('projection-comp', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵtext(0, 'Before');
- ɵɵprojection(1);
- ɵɵtext(2, 'After');
- }
- }, 3);
-
- /**
- *
- * A
- * 123
- *
- *
- * B
- * 456
- *
- */
- const AppComp = createComponent('app-comp', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'projection-comp');
- {
- ɵɵelementStart(1, 'div');
- { ɵɵtext(2, 'A'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'p');
- { ɵɵtext(4, '123'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- ɵɵelementStart(5, 'projection-comp');
- {
- ɵɵelementStart(6, 'div');
- { ɵɵtext(7, 'B'); }
- ɵɵelementEnd();
- ɵɵelementStart(8, 'p');
- { ɵɵtext(9, '456'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 10, 0, [ProjectionComp]);
-
- const fixture = new ComponentFixture(AppComp);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'BeforeA
123
After ' +
- 'BeforeB
456
After ');
- });
-
- it('should re-project with multiple instances of a component with projection', () => {
- /**
- * Before
- *
- * After
- */
- const ProjectionComp = createComponent('projection-comp', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵtext(0, 'Before');
- ɵɵprojection(1);
- ɵɵtext(2, 'After');
- }
- }, 3);
-
- /**
- *
- * A
- *
- * 123
- *
- *
- * B
- * 456
- *
- */
- const ProjectionParent = createComponent('parent-comp', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'projection-comp');
- {
- ɵɵelementStart(1, 'div');
- { ɵɵtext(2, 'A'); }
- ɵɵelementEnd();
- ɵɵprojection(3, 0);
- ɵɵelementStart(4, 'p');
- { ɵɵtext(5, '123'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- ɵɵelementStart(6, 'projection-comp');
- {
- ɵɵelementStart(7, 'div');
- { ɵɵtext(8, 'B'); }
- ɵɵelementEnd();
- ɵɵelementStart(9, 'p');
- { ɵɵtext(10, '456'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 11, 0, [ProjectionComp]);
-
- /**
- *
- * **ABC**
- *
- *
- * **DEF**
- *
- */
- const AppComp = createComponent('app-comp', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'parent-comp');
- { ɵɵtext(1, '**ABC**'); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'parent-comp');
- { ɵɵtext(3, '**DEF**'); }
- ɵɵelementEnd();
- }
- }, 4, 0, [ProjectionParent]);
-
- const fixture = new ComponentFixture(AppComp);
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '' +
- 'BeforeA
**ABC**123
After ' +
- 'BeforeB
456
After ' +
- '' +
- 'BeforeA
**DEF**123
After ' +
- 'BeforeB
456
After ');
- });
-
- it('should project ng-container at the content root', () => {
-
- ` `;
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵprojection(0);
- }
- }, 1);
-
- `
-
-
- content
-
-
- `;
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementContainerStart(1);
- {
- ɵɵelementContainerStart(2);
- { ɵɵtext(3, 'content'); }
- ɵɵelementContainerEnd();
- }
- ɵɵelementContainerEnd();
- }
- ɵɵelementEnd();
- }
- }, 4, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('content ');
- });
-
- it('should re-project ng-container at the content root', () => {
-
- ` `;
- const GrandChild = createComponent('grand-child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵprojection(0);
- }
- }, 1);
-
- `
-
- `;
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'grand-child');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }, 2, 0, [GrandChild]);
-
- `
-
-
- content
-
-
- `;
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementContainerStart(1);
- {
- ɵɵelementContainerStart(2);
- { ɵɵtext(3, 'content'); }
- ɵɵelementContainerEnd();
- }
- ɵɵelementContainerEnd();
- }
- ɵɵelementEnd();
- }
- }, 4, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('content ');
- });
-
- describe('with selectors', () => {
-
- it('should project nodes using attribute selectors', () => {
- /**
- *
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- // selectors are in lowercase once compiled
- ɵɵprojectionDef(['*', [['span', 'title', 'tofirst']], [['span', 'title', 'tosecond']]]);
- ɵɵelementStart(0, 'div', ['id', 'first']);
- { ɵɵprojection(1, 1); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'div', ['id', 'second']);
- { ɵɵprojection(3, 2); }
- ɵɵelementEnd();
- }
- }, 4);
-
- /**
- *
- * 1
- * 2
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'span', ['title', 'toFirst']);
- { ɵɵtext(2, '1'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'span', ['title', 'toSecond']);
- { ɵɵtext(4, '2'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 5, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent))
- .toEqual(
- '1
2
');
- });
-
- it('should project nodes using class selectors', () => {
- /**
- *
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- // selectors are in lowercase once compiled
- ɵɵprojectionDef([
- '*', [['span', SelectorFlags.CLASS, 'tofirst']],
- [['span', SelectorFlags.CLASS, 'tosecond']]
- ]);
- ɵɵelementStart(0, 'div', ['id', 'first']);
- { ɵɵprojection(1, 1); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'div', ['id', 'second']);
- { ɵɵprojection(3, 2); }
- ɵɵelementEnd();
- }
- }, 4);
-
- /**
- *
- * 1
- * 2
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'span', ['class', 'toFirst']);
- { ɵɵtext(2, '1'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'span', ['class', 'toSecond']);
- { ɵɵtext(4, '2'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 5, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent))
- .toEqual(
- '1
2
');
- });
-
- it('should project nodes using class selectors when element has multiple classes', () => {
- /**
- *
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- // selectors are in lowercase once compiled
- ɵɵprojectionDef([
- '*', [['span', SelectorFlags.CLASS, 'tofirst']],
- [['span', SelectorFlags.CLASS, 'tosecond']]
- ]);
- ɵɵelementStart(0, 'div', ['id', 'first']);
- { ɵɵprojection(1, 1); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'div', ['id', 'second']);
- { ɵɵprojection(3, 2); }
- ɵɵelementEnd();
- }
- }, 4);
-
- /**
- *
- * 1
- * 2
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'span', ['class', 'other toFirst']);
- { ɵɵtext(2, '1'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'span', ['class', 'toSecond noise']);
- { ɵɵtext(4, '2'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 5, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent))
- .toEqual(
- '1
2
');
- });
-
- it('should project nodes into the first matching selector', () => {
- /**
- *
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef(['*', [['span']], [['span', SelectorFlags.CLASS, 'toSecond']]]);
- ɵɵelementStart(0, 'div', ['id', 'first']);
- { ɵɵprojection(1, 1); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'div', ['id', 'second']);
- { ɵɵprojection(3, 2); }
- ɵɵelementEnd();
- }
- }, 4);
-
- /**
- *
- * 1
- * 2
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'span', ['class', 'toFirst']);
- { ɵɵtext(2, '1'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'span', ['class', 'toSecond']);
- { ɵɵtext(4, '2'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 5, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent))
- .toEqual(
- '1 2
');
- });
-
- it('should allow mixing ng-content with and without selectors', () => {
- /**
- *
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- // selectors are in lowercase once compiled
- ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'tofirst']]]);
- ɵɵelementStart(0, 'div', ['id', 'first']);
- { ɵɵprojection(1, 1); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'div', ['id', 'second']);
- { ɵɵprojection(3); }
- ɵɵelementEnd();
- }
- }, 4);
-
- /**
- *
- * 1
- * 2
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'span', ['class', 'toFirst']);
- { ɵɵtext(2, '1'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'span');
- { ɵɵtext(4, 'remaining'); }
- ɵɵelementEnd();
- ɵɵtext(5, 'more remaining');
- }
- ɵɵelementEnd();
- }
- }, 6, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent))
- .toEqual(
- '1
remaining more remaining
');
- });
-
- it('should allow mixing ng-content with and without selectors - ng-content first', () => {
- /**
- *
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- // selectors are in lowercase once compiled
- ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'tosecond']]]);
- ɵɵelementStart(0, 'div', ['id', 'first']);
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- ɵɵelementStart(2, 'div', ['id', 'second']);
- { ɵɵprojection(3, 1); }
- ɵɵelementEnd();
- }
- }, 4);
-
- /**
- *
- * 1
- * 2
- * remaining
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'span');
- { ɵɵtext(2, '1'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'span', ['class', 'toSecond']);
- { ɵɵtext(4, '2'); }
- ɵɵelementEnd();
- ɵɵtext(5, 'remaining');
- }
- ɵɵelementEnd();
- }
- }, 6, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent))
- .toEqual(
- '1 remaining
2
');
- });
-
- /**
- * Descending into projected content for selector-matching purposes is not supported
- * today: http://plnkr.co/edit/MYQcNfHSTKp9KvbzJWVQ?p=preview
- */
- it('should not descend into re-projected content', () => {
-
- /**
- *
- *
- *
- */
- const GrandChild = createComponent('grand-child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef(['*', [['span']]]);
- ɵɵprojection(0, 1);
- ɵɵelement(1, 'hr');
- ɵɵprojection(2);
- }
- }, 3);
-
- /**
- *
- *
- * in child template
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'grand-child');
- {
- ɵɵprojection(1);
- ɵɵelementStart(2, 'span');
- { ɵɵtext(3, 'in child template'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 4, 0, [GrandChild]);
-
- /**
- *
- *
- * parent content
- *
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'span');
- { ɵɵtext(2, 'parent content'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 3, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent))
- .toEqual(
- 'in child template parent content ');
- });
-
- it('should match selectors on ng-content nodes with attributes', () => {
-
- /**
- *
- *
- *
- */
- const Card = createComponent('card', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef(['*', [['', 'card-title', '']], [['', 'card-content', '']]]);
- ɵɵprojection(0, 1);
- ɵɵelement(1, 'hr');
- ɵɵprojection(2, 2);
- }
- }, 3);
-
- /**
- *
- * Title
- *
- *
- */
- const CardWithTitle = createComponent('card-with-title', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'card');
- {
- ɵɵelementStart(1, 'h1', ['card-title', '']);
- { ɵɵtext(2, 'Title'); }
- ɵɵelementEnd();
- ɵɵprojection(3, 0, ['card-content', '']);
- }
- ɵɵelementEnd();
- }
- }, 4, 0, [Card]);
-
- /**
- *
- * content
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'card-with-title');
- { ɵɵtext(1, 'content'); }
- ɵɵelementEnd();
- }
- }, 2, 0, [CardWithTitle]);
-
- const app = renderComponent(App);
- expect(toHtml(app))
- .toEqual(
- 'Title content ');
- });
-
- it('should not match selectors against node having ngProjectAs attribute', function() {
-
- /**
- *
- */
- const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef(['*', [['div']]]);
- ɵɵprojection(0, 1);
- }
- }, 1);
-
- /**
- *
- * should not project
- * should project
- *
- */
- const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵelementStart(1, 'div', [AttributeMarker.ProjectAs, ['span']]);
- { ɵɵtext(2, 'should not project'); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'div');
- { ɵɵtext(4, 'should project'); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- }, 5, 0, [Child]);
-
- const parent = renderComponent(Parent);
- expect(toHtml(parent)).toEqual('should project
');
- });
- });
-
-});