diff --git a/modules/angular2/src/render/dom/view/proto_view_builder.ts b/modules/angular2/src/render/dom/view/proto_view_builder.ts index 11fdc653f3..cdc0659f59 100644 --- a/modules/angular2/src/render/dom/view/proto_view_builder.ts +++ b/modules/angular2/src/render/dom/view/proto_view_builder.ts @@ -330,20 +330,27 @@ const STYLE_PREFIX = 'style'; function buildElementPropertyBindings( schemaRegistry: ElementSchemaRegistry, protoElement: /*element*/ any, isNgComponent: boolean, - bindingsInTemplate: Map, directiveTempaltePropertyNames: Set): + bindingsInTemplate: Map, directiveTemplatePropertyNames: Set): List { var propertyBindings = []; + MapWrapper.forEach(bindingsInTemplate, (ast, propertyNameInTemplate) => { var propertyBinding = createElementPropertyBinding(schemaRegistry, ast, propertyNameInTemplate); - if (isValidElementPropertyBinding(schemaRegistry, protoElement, isNgComponent, - propertyBinding)) { + + if (isPresent(directiveTemplatePropertyNames) && + SetWrapper.has(directiveTemplatePropertyNames, propertyNameInTemplate)) { + // We do nothing because directives shadow native elements properties. + + } else if (isValidElementPropertyBinding(schemaRegistry, protoElement, isNgComponent, + propertyBinding)) { propertyBindings.push(propertyBinding); - } else if (!isPresent(directiveTempaltePropertyNames) || - !SetWrapper.has(directiveTempaltePropertyNames, propertyNameInTemplate)) { - // directiveTempaltePropertyNames is null for host property bindings + + } else { var exMsg = `Can't bind to '${propertyNameInTemplate}' since it isn't a known property of the '<${DOM.tagName(protoElement).toLowerCase()}>' element`; - if (isPresent(directiveTempaltePropertyNames)) { + + // directiveTemplatePropertyNames is null for host property bindings + if (isPresent(directiveTemplatePropertyNames)) { exMsg += ' and there are no matching directives with a corresponding property'; } throw new BaseException(exMsg); diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 57884eb88a..a5b24887a0 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -31,6 +31,7 @@ import { isJsObject, global, stringify, + isBlank, CONST, CONST_EXPR } from 'angular2/src/facade/lang'; @@ -1359,8 +1360,8 @@ export function main() { }); })); - if (!IS_DARTIUM) { - describe('Missing property bindings', () => { + describe('Property bindings', () => { + if (!IS_DARTIUM) { it('should throw on bindings to unknown properties', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { @@ -1386,8 +1387,47 @@ export function main() { .createAsync(MyComp) .then((val) => { async.done(); }); })); - }); - } + } + + it('should not be created when there is a directive with the same property', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MyComp, new viewAnn.View({ + template: '', + directives: [DirectiveWithTitle] + })) + .createAsync(MyComp) + .then((rootTC) => { + rootTC.componentInstance.ctxProp = "TITLE"; + rootTC.detectChanges(); + + var el = DOM.querySelector(rootTC.nativeElement, "span"); + expect(isBlank(el.title) || el.title == '').toBeTruthy(); + + async.done(); + + }); + })); + + it('should work when a directive uses hostProperty to update the DOM element', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MyComp, new viewAnn.View({ + template: '', + directives: [DirectiveWithTitleAndHostProperty] + })) + .createAsync(MyComp) + .then((rootTC) => { + rootTC.componentInstance.ctxProp = "TITLE"; + rootTC.detectChanges(); + + var el = DOM.querySelector(rootTC.nativeElement, "span"); + expect(el.title).toEqual("TITLE"); + + async.done(); + + }); + })); + }); + describe('different proto view storages', () => { function runWithMode(mode: string) { @@ -1505,6 +1545,16 @@ class MyDir { constructor() { this.dirProp = ''; } } +@Directive({selector: '[title]', properties: ['title']}) +class DirectiveWithTitle { + title: string; +} + +@Directive({selector: '[title]', properties: ['title'], host: {'[title]': 'title'}}) +class DirectiveWithTitleAndHostProperty { + title: string; +} + @Component({selector: 'push-cmp', properties: ['prop'], changeDetection: ON_PUSH}) @View({template: '{{field}}'}) @Injectable() diff --git a/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts b/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts index d21e945c1f..913cff5b0e 100644 --- a/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts +++ b/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts @@ -104,42 +104,53 @@ export function main() { var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].property).toEqual('readOnly'); }); - }); - describe('property binding types', () => { - it('should detect property names', () => { - builder.bindElement(el('
')).bindProperty('tabindex', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); - expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.PROPERTY); + describe('property binding', () => { + describe('types', () => { + it('should detect property names', () => { + builder.bindElement(el('
')).bindProperty('tabindex', emptyExpr()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); + expect(pv.elementBinders[0].propertyBindings[0].type) + .toEqual(PropertyBindingType.PROPERTY); + }); + + it('should detect attribute names', () => { + builder.bindElement(el('
')).bindProperty('attr.someName', emptyExpr()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); + expect(pv.elementBinders[0].propertyBindings[0].type) + .toEqual(PropertyBindingType.ATTRIBUTE); + }); + + it('should detect class names', () => { + builder.bindElement(el('
')).bindProperty('class.someName', emptyExpr()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); + expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.CLASS); + }); + + it('should detect style names', () => { + builder.bindElement(el('
')).bindProperty('style.someName', emptyExpr()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); + expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.STYLE); + }); + + it('should detect style units', () => { + builder.bindElement(el('
')).bindProperty('style.someName.someUnit', emptyExpr()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); + expect(pv.elementBinders[0].propertyBindings[0].unit).toEqual('someUnit'); + }); }); - it('should detect attribute names', () => { - builder.bindElement(el('
')).bindProperty('attr.someName', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); - expect(pv.elementBinders[0].propertyBindings[0].type) - .toEqual(PropertyBindingType.ATTRIBUTE); - }); + it('should not create a property binding when there is already same directive property binding', + () => { + var binder = builder.bindElement(el('
')); - it('should detect class names', () => { - builder.bindElement(el('
')).bindProperty('class.someName', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); - expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.CLASS); - }); + binder.bindProperty('tabindex', emptyExpr()); + binder.bindDirective(0).bindProperty('tabindex', emptyExpr(), 'tabindex'); - it('should detect style names', () => { - builder.bindElement(el('
')).bindProperty('style.someName', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); - expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.STYLE); - }); - - it('should detect style units', () => { - builder.bindElement(el('
')).bindProperty('style.someName.someUnit', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); - expect(pv.elementBinders[0].propertyBindings[0].unit).toEqual('someUnit'); - }); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); + expect(pv.elementBinders[0].propertyBindings.length).toEqual(0); + }); }); - - }); }