From 903ff9047f34722051f322a9dcafe36635b46bf3 Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Wed, 15 Jul 2015 10:55:44 -0700 Subject: [PATCH] feat(core): add ability to reflect DOM properties as attributes By binding the token `DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES` provided by the dom_renderer module to `true` in the root injector (i.e. bootstrap()), all elements whose properties are set by angular will be reflected as attributes with the prefix "ng-reflect-". Fixes #2910 --- modules/angular2/angular2.ts | 6 ++- modules/angular2/src/core/application.ts | 7 ++- .../angular2/src/render/dom/dom_renderer.ts | 16 ++++++- .../angular2/src/test_lib/test_injector.ts | 7 ++- .../dom/dom_renderer_integration_spec.ts | 46 +++++++++++++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/modules/angular2/angular2.ts b/modules/angular2/angular2.ts index 5ad4841be628f..1c807202756f1 100644 --- a/modules/angular2/angular2.ts +++ b/modules/angular2/angular2.ts @@ -64,4 +64,8 @@ export { RenderViewRef, RenderProtoViewRef } from 'angular2/src/render/api'; -export {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +export { + DomRenderer, + DOCUMENT_TOKEN, + DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES +} from 'angular2/src/render/dom/dom_renderer'; diff --git a/modules/angular2/src/core/application.ts b/modules/angular2/src/core/application.ts index 19ea53a632011..9988f1b68cea2 100644 --- a/modules/angular2/src/core/application.ts +++ b/modules/angular2/src/core/application.ts @@ -56,7 +56,11 @@ import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils import {AppViewListener} from 'angular2/src/core/compiler/view_listener'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {Renderer, RenderCompiler} from 'angular2/src/render/api'; -import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import { + DomRenderer, + DOCUMENT_TOKEN, + DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES +} from 'angular2/src/render/dom/dom_renderer'; import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; import {internalView} from 'angular2/src/core/compiler/view_ref'; @@ -77,6 +81,7 @@ function _injectorBindings(appComponentType): List> { return [ bind(DOCUMENT_TOKEN) .toValue(DOM.defaultDoc()), + bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false), bind(appComponentTypeToken).toValue(appComponentType), bind(appComponentRefPromiseToken) .toFactory( diff --git a/modules/angular2/src/render/dom/dom_renderer.ts b/modules/angular2/src/render/dom/dom_renderer.ts index 41962e3529bf9..dfa289057071b 100644 --- a/modules/angular2/src/render/dom/dom_renderer.ts +++ b/modules/angular2/src/render/dom/dom_renderer.ts @@ -18,19 +18,26 @@ import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view import {DomView, DomViewRef, resolveInternalDomView} from './view/view'; import {DomElement} from './view/element'; import {DomViewContainer} from './view/view_container'; -import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from './util'; +import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS, camelCaseToDashCase} from './util'; import {Renderer, RenderProtoViewRef, RenderViewRef, RenderElementRef} from '../api'; export const DOCUMENT_TOKEN = CONST_EXPR(new OpaqueToken('DocumentToken')); +export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES = + CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes')); +const REFLECT_PREFIX = 'ng-reflect-'; @Injectable() export class DomRenderer extends Renderer { _document; + _reflectPropertiesAsAttributes: boolean; constructor(public _eventManager: EventManager, public _shadowDomStrategy: ShadowDomStrategy, - @Inject(DOCUMENT_TOKEN) document) { + @Inject(DOCUMENT_TOKEN) document, + @Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes: + boolean) { super(); + this._reflectPropertiesAsAttributes = reflectPropertiesAsAttributes; this._document = document; } @@ -186,6 +193,11 @@ export class DomRenderer extends Renderer { setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any): void { var view = resolveInternalDomView(location.renderView); view.setElementProperty(location.boundElementIndex, propertyName, propertyValue); + // Reflect the property value as an attribute value with ng-reflect- prefix. + if (this._reflectPropertiesAsAttributes) { + this.setElementAttribute(location, `${REFLECT_PREFIX}${camelCaseToDashCase(propertyName)}`, + propertyValue); + } } setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string): diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts index 41c3dd9a42fa4..3160824311bd3 100644 --- a/modules/angular2/src/test_lib/test_injector.ts +++ b/modules/angular2/src/test_lib/test_injector.ts @@ -50,7 +50,11 @@ import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils import {ELEMENT_PROBE_CONFIG} from 'angular2/debug'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {RenderCompiler, Renderer} from 'angular2/src/render/api'; -import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import { + DomRenderer, + DOCUMENT_TOKEN, + DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES +} from 'angular2/src/render/dom/dom_renderer'; import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; /** @@ -90,6 +94,7 @@ function _getAppBindings() { .toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]), DomRenderer, DefaultDomCompiler, + bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false), bind(Renderer).toAlias(DomRenderer), bind(RenderCompiler).toAlias(DefaultDomCompiler), ProtoViewFactory, diff --git a/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts b/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts index 0b2ed4fb7e9e9..501813da17192 100644 --- a/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts +++ b/modules/angular2/test/render/dom/dom_renderer_integration_spec.ts @@ -19,6 +19,8 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {DomTestbed, TestView, elRef} from './dom_testbed'; import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api'; +import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer'; +import {bind} from 'angular2/di'; export function main() { describe('DomRenderer integration', () => { @@ -126,6 +128,50 @@ export function main() { }); })); + + it('should NOT reflect property values as attributes if flag is NOT set', + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + someComponent, + new ViewDefinition( + {componentId: 'someComponent', template: '', directives: []}) + ]) + .then((protoViewDtos) => { + var rootView = tb.createRootView(protoViewDtos[0]); + var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]); + var el = DOM.childNodes(tb.rootEl)[0]; + tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20'); + expect(DOM.getAttribute(el, 'ng-reflect-max-length')) + .toEqual(null); + + async.done(); + }); + })); + + describe('reflection', () => { + beforeEachBindings(() => [bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(true)]); + it('should reflect property values as attributes if flag is set', + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + someComponent, + new ViewDefinition({ + componentId: 'someComponent', + template: '', + directives: [] + }) + ]) + .then((protoViewDtos) => { + var rootView = tb.createRootView(protoViewDtos[0]); + var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]); + var el = DOM.childNodes(tb.rootEl)[0]; + tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20'); + expect(DOM.getAttribute(el, 'ng-reflect-max-length')) + .toEqual('20'); + async.done(); + }); + })); + }); + if (DOM.supportsDOMEvents()) { it('should call actions on the element independent of the compilation', inject([AsyncTestCompleter, DomTestbed], (async, tb) => {