diff --git a/modules/@angular/common/src/directives/ng_template_outlet.ts b/modules/@angular/common/src/directives/ng_template_outlet.ts index d72facdc9b46a4..33f2776920c023 100644 --- a/modules/@angular/common/src/directives/ng_template_outlet.ts +++ b/modules/@angular/common/src/directives/ng_template_outlet.ts @@ -1,30 +1,53 @@ -import {Directive, Input, TemplateRef, ViewContainerRef, ViewRef} from '@angular/core'; - +import {Directive, Input, TemplateRef, ViewContainerRef, EmbeddedViewRef} from '@angular/core'; import {isPresent} from '../facade/lang'; /** * Creates and inserts an embedded view based on a prepared `TemplateRef`. + * You can attach a context object to the `EmbeddedViewRef` by setting `[ngOutletContext]`. + * `[ngOutletContext]` should be an object, the object's keys will be the local template variables + * available within the `TemplateRef`. + * + * Note: using the key `$implicit` in the context object will set it's value as default. * * ### Syntax - * - `` + * - `` * * @experimental */ @Directive({selector: '[ngTemplateOutlet]'}) export class NgTemplateOutlet { - private _insertedViewRef: ViewRef; + private _viewRef: EmbeddedViewRef; + private _context: Object; + private _templateRef: TemplateRef; constructor(private _viewContainerRef: ViewContainerRef) {} + @Input() + set ngOutletContext(context: Object) { + if (this._context !== context) { + this._context = context; + if (isPresent(this._viewRef)) { + this.createView(); + } + } + } + @Input() set ngTemplateOutlet(templateRef: TemplateRef) { - if (isPresent(this._insertedViewRef)) { - this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._insertedViewRef)); + if (this._templateRef !== templateRef) { + this._templateRef = templateRef; + this.createView(); + } + } + + private createView() { + if (isPresent(this._viewRef)) { + this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef)); } - if (isPresent(templateRef)) { - this._insertedViewRef = this._viewContainerRef.createEmbeddedView(templateRef); + if (isPresent(this._templateRef)) { + this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef, this._context); } } } diff --git a/modules/@angular/common/test/directives/ng_template_outlet_spec.ts b/modules/@angular/common/test/directives/ng_template_outlet_spec.ts index f519d6ad29ca40..89d1c7b158c25b 100644 --- a/modules/@angular/common/test/directives/ng_template_outlet_spec.ts +++ b/modules/@angular/common/test/directives/ng_template_outlet_spec.ts @@ -95,6 +95,91 @@ export function main() { }); })); + it('should display template if context is null', + 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].references['refs']; + + fixture.componentInstance.currentTplRef = refs.tplRefs.first; + fixture.detectChanges(); + expect(fixture.nativeElement).toHaveText('foo'); + + async.done(); + }); + })); + + it('should reflect initial context and 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].references['refs']; + fixture.componentInstance.currentTplRef = refs.tplRefs.first; + + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement).toHaveText('bar'); + + fixture.componentInstance.context.foo = 'alter-bar'; + + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement).toHaveText('alter-bar'); + + async.done(); + }); + })); + + it('should reflect user defined $implicit property in the context', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + var template =``; + tcb.overrideTemplate(TestComponent, template) + .createAsync(TestComponent) + .then((fixture) => { + fixture.detectChanges(); + + var refs = fixture.debugElement.children[0].references['refs']; + fixture.componentInstance.currentTplRef = refs.tplRefs.first; + + fixture.componentInstance.context = { $implicit: fixture.componentInstance.context }; + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement).toHaveText('bar'); + + async.done(); + }); + })); + + it('should reflect context re-binding', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + var template =``; + tcb.overrideTemplate(TestComponent, template) + .createAsync(TestComponent) + .then((fixture) => { + fixture.detectChanges(); + + var refs = fixture.debugElement.children[0].references['refs']; + fixture.componentInstance.currentTplRef = refs.tplRefs.first; + fixture.componentInstance.context = { shawshank: 'brooks' }; + + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement).toHaveText('brooks'); + + fixture.componentInstance.context = { shawshank: 'was here' }; + + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement).toHaveText('was here'); + + async.done(); + }); + })); }); } @@ -107,4 +192,5 @@ class CaptureTplRefs { @Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''}) class TestComponent { currentTplRef: TemplateRef; + context: any = { foo: 'bar' }; } diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index d0ebd1d2112b36..ab4d92adeb06c1 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -988,6 +988,7 @@ const COMMON = [ 'NgSwitchDefault.constructor(viewContainer:ViewContainerRef, templateRef:TemplateRef, sswitch:NgSwitch)', 'NgTemplateOutlet', 'NgTemplateOutlet.constructor(_viewContainerRef:ViewContainerRef)', + 'NgTemplateOutlet.ngOutletContext=(context:Object)', 'NgTemplateOutlet.ngTemplateOutlet=(templateRef:TemplateRef)', 'PathLocationStrategy', 'PathLocationStrategy.back():void',