Skip to content

Commit

Permalink
feat(NgTemplateOutlet): add context to NgTemplateOutlet
Browse files Browse the repository at this point in the history
Closes #9042
  • Loading branch information
shlomiassaf authored and mhevery committed Jun 10, 2016
1 parent 4ed6cf7 commit 164a091
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 8 deletions.
39 changes: 31 additions & 8 deletions modules/@angular/common/src/directives/ng_template_outlet.ts
Original file line number Diff line number Diff line change
@@ -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
* - `<template [ngTemplateOutlet]="templateRefExpression"></template>`
* - `<template [ngTemplateOutlet]="templateRefExpression" [ngOutletContext]="objectExpression"></template>`
*
* @experimental
*/
@Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet {
private _insertedViewRef: ViewRef;
private _viewRef: EmbeddedViewRef<any>;
private _context: Object;
private _templateRef: TemplateRef<any>;

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<Object>) {
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);
}
}
}
86 changes: 86 additions & 0 deletions modules/@angular/common/test/directives/ng_template_outlet_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,91 @@ export function main() {
});
}));

it('should display template if context is null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></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 =`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></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 =`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></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 =`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></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();
});
}));
});
}

Expand All @@ -107,4 +192,5 @@ class CaptureTplRefs {
@Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''})
class TestComponent {
currentTplRef: TemplateRef<any>;
context: any = { foo: 'bar' };
}
1 change: 1 addition & 0 deletions tools/public_api_guard/public_api_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,7 @@ const COMMON = [
'NgSwitchDefault.constructor(viewContainer:ViewContainerRef, templateRef:TemplateRef<Object>, sswitch:NgSwitch)',
'NgTemplateOutlet',
'NgTemplateOutlet.constructor(_viewContainerRef:ViewContainerRef)',
'NgTemplateOutlet.ngOutletContext=(context:Object)',
'NgTemplateOutlet.ngTemplateOutlet=(templateRef:TemplateRef<Object>)',
'PathLocationStrategy',
'PathLocationStrategy.back():void',
Expand Down

0 comments on commit 164a091

Please sign in to comment.