From b6d5a065c866ac6f44d8fd49d614508cce317351 Mon Sep 17 00:00:00 2001 From: Shlomi Assaf Date: Mon, 6 Jun 2016 19:03:32 +0300 Subject: [PATCH 1/3] feat(NgTemplateOutlet): add context to NgTemplateOutlet --- .../src/directives/ng_template_outlet.ts | 38 ++++++-- .../directives/ng_template_outlet_spec.ts | 86 +++++++++++++++++++ 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/modules/@angular/common/src/directives/ng_template_outlet.ts b/modules/@angular/common/src/directives/ng_template_outlet.ts index 7135ec3661bd9..356017d49b1cb 100644 --- a/modules/@angular/common/src/directives/ng_template_outlet.ts +++ b/modules/@angular/common/src/directives/ng_template_outlet.ts @@ -1,28 +1,52 @@ -import {Directive, Input, ViewContainerRef, ViewRef, TemplateRef} from '@angular/core'; +import {Directive, Input, ViewContainerRef, EmbeddedViewRef, TemplateRef} 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 d57272d91e01e..b51e185089c31 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' }; } From 2d9c831e5b3b2643ae82275195f306a577fc672b Mon Sep 17 00:00:00 2001 From: shlomiassaf Date: Mon, 6 Jun 2016 19:27:33 +0300 Subject: [PATCH 2/3] chore: fix public api spec for `ngOutletContext` --- tools/public_api_guard/public_api_spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index 32610e0206993..e82a3cd2423ff 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -976,6 +976,7 @@ const COMMON = [ 'NgSwitchWhen.ngSwitchWhen=(value:any)', 'NgTemplateOutlet', 'NgTemplateOutlet.constructor(_viewContainerRef:ViewContainerRef)', + 'NgTemplateOutlet.ngOutletContext=(context:Object)', 'NgTemplateOutlet.ngTemplateOutlet=(templateRef:TemplateRef)', 'PathLocationStrategy', 'PathLocationStrategy.back():void', From 6bd895cbb896980703b1c60fd124e311a14db6d3 Mon Sep 17 00:00:00 2001 From: shlomiassaf Date: Mon, 6 Jun 2016 21:23:29 +0300 Subject: [PATCH 3/3] docs(NgTemplateOutlet): fix typo --- modules/@angular/common/src/directives/ng_template_outlet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/@angular/common/src/directives/ng_template_outlet.ts b/modules/@angular/common/src/directives/ng_template_outlet.ts index 356017d49b1cb..3f599c25e202b 100644 --- a/modules/@angular/common/src/directives/ng_template_outlet.ts +++ b/modules/@angular/common/src/directives/ng_template_outlet.ts @@ -10,7 +10,7 @@ import {isPresent} from '../facade/lang'; * Note: using the key `$implicit` in the context object will set it's value as default. * * ### Syntax - * - `` + * - `` * * @experimental */