feat(NgTemplateOutlet): add context to NgTemplateOutlet

Closes #9042
This commit is contained in:
Shlomi Assaf 2016-06-06 19:03:32 +03:00 committed by Misko Hevery
parent 4ed6cf7519
commit 164a091c71
3 changed files with 118 additions and 8 deletions

View File

@ -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 ngTemplateOutlet(templateRef: TemplateRef<Object>) {
if (isPresent(this._insertedViewRef)) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._insertedViewRef));
set ngOutletContext(context: Object) {
if (this._context !== context) {
this._context = context;
if (isPresent(this._viewRef)) {
this.createView();
}
}
}
if (isPresent(templateRef)) {
this._insertedViewRef = this._viewContainerRef.createEmbeddedView(templateRef);
@Input()
set ngTemplateOutlet(templateRef: TemplateRef<Object>) {
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(this._templateRef)) {
this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef, this._context);
}
}
}

View File

@ -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();
});
}));
});
}
@ -107,4 +192,5 @@ class CaptureTplRefs {
@Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''})
class TestComponent {
currentTplRef: TemplateRef<any>;
context: any = { foo: 'bar' };
}

View File

@ -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',