diff --git a/modules/angular2/src/common/directives.ts b/modules/angular2/src/common/directives.ts index ceb662d168..e81c64a200 100644 --- a/modules/angular2/src/common/directives.ts +++ b/modules/angular2/src/common/directives.ts @@ -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'; diff --git a/modules/angular2/src/common/directives/core_directives.ts b/modules/angular2/src/common/directives/core_directives.ts index 64772e101a..fed030c5c2 100644 --- a/modules/angular2/src/common/directives/core_directives.ts +++ b/modules/angular2/src/common/directives/core_directives.ts @@ -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, diff --git a/modules/angular2/src/common/directives/ng_template_outlet.ts b/modules/angular2/src/common/directives/ng_template_outlet.ts new file mode 100644 index 0000000000..38784bbad4 --- /dev/null +++ b/modules/angular2/src/common/directives/ng_template_outlet.ts @@ -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 + * - `` + */ +@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); + } + } +} diff --git a/modules/angular2/test/common/directives/ng_template_outlet_spec.ts b/modules/angular2/test/common/directives/ng_template_outlet_spec.ts new file mode 100644 index 0000000000..158188f206 --- /dev/null +++ b/modules/angular2/test/common/directives/ng_template_outlet_spec.ts @@ -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 = ``; + 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 = + ``; + 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 = + ``; + 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 = ``; + 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; +} + +@Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''}) +class TestComponent { + currentTplRef: TemplateRef; +} diff --git a/modules/angular2/test/public_api_spec.ts b/modules/angular2/test/public_api_spec.ts index e365717eaa..0674737ce4 100644 --- a/modules/angular2/test/public_api_spec.ts +++ b/modules/angular2/test/public_api_spec.ts @@ -66,6 +66,7 @@ var NG_COMMON = [ 'NgFormControl', 'NgFormModel', 'NgIf', + 'NgTemplateOutlet', 'NgModel', 'NgSelectOption', 'NgStyle', diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index d2a87aa9f5..7ef838ec1d 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -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',