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 = `foo `;
+ 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 =`{{foo}} `;
+ 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 =`{{ctx.foo}} `;
+ 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 =`{{shawshank}} `;
+ 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',