feat(NgTemplateOutlet): add NgTemplateOutlet directive

This commits adds a new NgTemplateOutlet directive that can be
used to create embeded views from a supplied TemplateRef.

Closes #7615

Closes #8021
This commit is contained in:
Pawel Kozlowski 2016-04-11 17:47:28 +02:00
parent b602bd8c83
commit f4e6994634
6 changed files with 146 additions and 0 deletions

View File

@ -6,6 +6,7 @@
export {NgClass} from './directives/ng_class';
export {NgFor} from './directives/ng_for';
export {NgIf} from './directives/ng_if';
export {NgTemplateOutlet} from './directives/ng_template_outlet';
export {NgStyle} from './directives/ng_style';
export {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './directives/ng_switch';
export {NgPlural, NgPluralCase, NgLocalization} from './directives/ng_plural';

View File

@ -2,6 +2,7 @@ import {CONST_EXPR, Type} from 'angular2/src/facade/lang';
import {NgClass} from './ng_class';
import {NgFor} from './ng_for';
import {NgIf} from './ng_if';
import {NgTemplateOutlet} from './ng_template_outlet';
import {NgStyle} from './ng_style';
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './ng_switch';
import {NgPlural, NgPluralCase} from './ng_plural';
@ -50,6 +51,7 @@ export const CORE_DIRECTIVES: Type[] = CONST_EXPR([
NgClass,
NgFor,
NgIf,
NgTemplateOutlet,
NgStyle,
NgSwitch,
NgSwitchWhen,

View File

@ -0,0 +1,26 @@
import {Directive, Input, ViewContainerRef, ViewRef, TemplateRef} from 'angular2/core';
import {isPresent} from 'angular2/src/facade/lang';
/**
* Creates and inserts an embedded view based on a prepared `TemplateRef`.
*
* ### Syntax
* - `<template [ngTemplateOutlet]="templateRefExpression"></template>`
*/
@Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet {
private _insertedViewRef: ViewRef;
constructor(private _viewContainerRef: ViewContainerRef) {}
@Input()
set ngTemplateOutlet(templateRef: TemplateRef) {
if (isPresent(this._insertedViewRef)) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._insertedViewRef));
}
if (isPresent(templateRef)) {
this._insertedViewRef = this._viewContainerRef.createEmbeddedView(templateRef);
}
}
}

View File

@ -0,0 +1,113 @@
import {
AsyncTestCompleter,
TestComponentBuilder,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
} from 'angular2/testing_internal';
import {Component, Directive, TemplateRef, ContentChildren, QueryList} from 'angular2/core';
import {NgTemplateOutlet} from 'angular2/src/common/directives/ng_template_outlet';
export function main() {
describe('insert', () => {
it('should do nothing if templateRef is null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<template [ngTemplateOutlet]="null"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
async.done();
});
}));
it('should insert content specified by TemplateRef',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
var refs = fixture.debugElement.children[0].getLocal('refs');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
async.done();
});
}));
it('should clear content if TemplateRef becomes null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].getLocal('refs');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
async.done();
});
}));
it('should swap content if TemplateRef changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].getLocal('refs');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.last;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bar');
async.done();
});
}));
});
}
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
class CaptureTplRefs {
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef>;
}
@Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''})
class TestComponent {
currentTplRef: TemplateRef;
}

View File

@ -66,6 +66,7 @@ var NG_COMMON = [
'NgFormControl',
'NgFormModel',
'NgIf',
'NgTemplateOutlet',
'NgModel',
'NgSelectOption',
'NgStyle',

View File

@ -736,6 +736,9 @@ const COMMON = [
'NgIf',
'NgIf.constructor(_viewContainer:ViewContainerRef, _templateRef:TemplateRef)',
'NgIf.ngIf=(newCondition:any)',
'NgTemplateOutlet',
'NgTemplateOutlet.constructor(_viewContainerRef:ViewContainerRef)',
'NgTemplateOutlet.ngTemplateOutlet=(templateRef:TemplateRef)',
'NgLocalization',
'NgLocalization.getPluralCategory(value:any):string',
'NgModel',