diff --git a/modules/angular2/src/core/compiler/view.ts b/modules/angular2/src/core/compiler/view.ts index 691d9c4cf5..7e8644ff15 100644 --- a/modules/angular2/src/core/compiler/view.ts +++ b/modules/angular2/src/core/compiler/view.ts @@ -183,7 +183,8 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher { if (b.isElementProperty()) { this.renderer.setElementProperty(elementRef, b.name, currentValue); } else if (b.isElementAttribute()) { - this.renderer.setElementAttribute(elementRef, b.name, `${currentValue}`); + this.renderer.setElementAttribute(elementRef, b.name, + isPresent(currentValue) ? `${currentValue}` : null); } else if (b.isElementClass()) { this.renderer.setElementClass(elementRef, b.name, currentValue); } else if (b.isElementStyle()) { diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index fe8199de81..720178366e 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -167,6 +167,30 @@ export function main() { }); })); + it('should remove an attribute when attribute expression evaluates to null', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MyComp, + new ViewMetadata({template: '
'})) + + .createAsync(MyComp) + .then((rootTC) => { + + rootTC.debugElement.componentInstance.ctxProp = 'bar'; + rootTC.detectChanges(); + expect(DOM.getAttribute(rootTC.debugElement.componentViewChildren[0].nativeElement, + 'foo')) + .toEqual('bar'); + + rootTC.debugElement.componentInstance.ctxProp = null; + rootTC.detectChanges(); + expect(DOM.hasAttribute(rootTC.debugElement.componentViewChildren[0].nativeElement, + 'foo')) + .toBeFalsy(); + + async.done(); + }); + })); + it('should consume binding to property names where attr name and property name do not match', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MyComp,