feat(ivy): add basic support for ng-container (#25227)

This commit adds basic support for <ng-container> - most of the
functionality should work as long as <ng-container> is a child of
a regular element.

PR Close #25227
This commit is contained in:
Pawel Kozlowski
2018-07-26 17:22:41 +02:00
committed by Kara Erickson
parent 4f741e74e1
commit 28c7a4efbc
9 changed files with 236 additions and 26 deletions

View File

@ -6,16 +6,17 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ElementRef} from '@angular/core';
import {RenderFlags} from '@angular/core/src/render3';
import {defineComponent, defineDirective} from '../../src/render3/index';
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
import {AttributeMarker, defineComponent, defineDirective, injectElementRef} from '../../src/render3/index';
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, listener, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
import {HEADER_OFFSET} from '../../src/render3/interfaces/view';
import {sanitizeUrl} from '../../src/sanitization/sanitization';
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
import {ComponentFixture, containerEl, renderToHtml} from './render_util';
import {ComponentFixture, TemplateFixture, containerEl, renderToHtml} from './render_util';
describe('render3 integration test', () => {
@ -418,6 +419,104 @@ describe('render3 integration test', () => {
});
describe('ng-container', () => {
it('should insert as a child of a regular element', () => {
/**
* <div>before|<ng-container>Greetings<span></span></ng-container>|after</div>
*/
function Template() {
elementStart(0, 'div');
{
text(1, 'before|');
elementContainerStart(2);
{
text(3, 'Greetings');
element(4, 'span');
}
elementContainerEnd();
text(5, '|after');
}
elementEnd();
}
const fixture = new TemplateFixture(Template);
expect(fixture.html).toEqual('<div>before|Greetings<span></span>|after</div>');
});
it('should support directives and inject ElementRef', () => {
class Directive {
constructor(public elRef: ElementRef) {}
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => new Directive(injectElementRef()),
});
}
let directive: Directive;
/**
* <div><ng-container dir></ng-container></div>
*/
function Template() {
elementStart(0, 'div');
{
elementContainerStart(1, [AttributeMarker.SelectOnly, 'dir']);
elementContainerEnd();
directive = loadDirective<Directive>(0);
}
elementEnd();
}
const fixture = new TemplateFixture(Template, () => {}, [Directive]);
expect(fixture.html).toEqual('<div></div>');
expect(directive !.elRef.nativeElement.nodeType).toBe(Node.COMMENT_NODE);
});
it('should not set any attributes', () => {
/**
* <div><ng-container id="foo"></ng-container></div>
*/
function Template() {
elementStart(0, 'div');
{
elementContainerStart(1, ['id', 'foo']);
elementContainerEnd();
}
elementEnd();
}
const fixture = new TemplateFixture(Template);
expect(fixture.html).toEqual('<div></div>');
});
it('should throw when trying to add event listener', () => {
/**
* <div><ng-container (click)="..."></ng-container></div>
*/
function Template() {
elementStart(0, 'div');
{
elementContainerStart(1);
{
listener('click', function() {});
}
elementContainerEnd();
}
elementEnd();
}
expect(() => { new TemplateFixture(Template); }).toThrow();
});
});
describe('tree', () => {
interface Tree {
beforeLabel?: string;

View File

@ -12,7 +12,7 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {EventEmitter} from '../..';
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectViewContainerRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {query, queryRefresh} from '../../src/render3/query';
@ -364,6 +364,43 @@ describe('query', () => {
expect(qList.first.nativeElement).toEqual(elToQuery);
});
it('should query for <ng-container> and read ElementRef with a native element pointing to comment node',
() => {
let elToQuery;
/**
* <ng-container #foo></ng-container>
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent(
'cmpt',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementContainerStart(1, null, ['foo', '']);
elToQuery = loadElement(1).native;
elementContainerEnd();
}
},
[], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
query(0, ['foo'], false, QUERY_READ_ELEMENT_REF);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = load<QueryList<any>>(0)) &&
(ctx.query = tmp as QueryList<any>);
}
});
const cmptInstance = renderComponent(Cmpt);
const qList = (cmptInstance.query as QueryList<any>);
expect(qList.length).toBe(1);
expect(isElementRef(qList.first)).toBeTruthy();
expect(qList.first.nativeElement).toEqual(elToQuery);
});
it('should read ViewContainerRef from element nodes when explicitly asked for', () => {
/**
* <div #foo></div>

View File

@ -222,7 +222,8 @@ export function toHtml<T>(componentOrElement: T | RElement): string {
.replace(/^<div fixture="mark">/, '')
.replace(/<\/div>$/, '')
.replace(' style=""', '')
.replace(/<!--container-->/g, '');
.replace(/<!--container-->/g, '')
.replace(/<!--ng-container-->/g, '');
}
}