Skip to content

Commit bb708d5

Browse files
committed
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 angular#2910
1 parent 93055f7 commit bb708d5

File tree

5 files changed

+74
-5
lines changed

5 files changed

+74
-5
lines changed

modules/angular2/angular2.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,8 @@ export {
6464
RenderViewRef,
6565
RenderProtoViewRef
6666
} from 'angular2/src/render/api';
67-
export {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
67+
export {
68+
DomRenderer,
69+
DOCUMENT_TOKEN,
70+
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
71+
} from 'angular2/src/render/dom/dom_renderer';

modules/angular2/src/core/application.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils
5656
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
5757
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
5858
import {Renderer, RenderCompiler} from 'angular2/src/render/api';
59-
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
59+
import {
60+
DomRenderer,
61+
DOCUMENT_TOKEN,
62+
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
63+
} from 'angular2/src/render/dom/dom_renderer';
6064
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
6165
import {internalView} from 'angular2/src/core/compiler/view_ref';
6266

@@ -77,6 +81,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
7781
return [
7882
bind(DOCUMENT_TOKEN)
7983
.toValue(DOM.defaultDoc()),
84+
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
8085
bind(appComponentTypeToken).toValue(appComponentType),
8186
bind(appComponentRefPromiseToken)
8287
.toFactory(

modules/angular2/src/render/dom/dom_renderer.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,26 @@ import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view
1818
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
1919
import {DomElement} from './view/element';
2020
import {DomViewContainer} from './view/view_container';
21-
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from './util';
21+
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS, camelCaseToDashCase} from './util';
2222

2323
import {Renderer, RenderProtoViewRef, RenderViewRef, RenderElementRef} from '../api';
2424

2525
export const DOCUMENT_TOKEN = CONST_EXPR(new OpaqueToken('DocumentToken'));
26+
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES =
27+
CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes'));
28+
const REFLECT_PREFIX = CONST_EXPR('ng-reflect-');
2629

2730
@Injectable()
2831
export class DomRenderer extends Renderer {
2932
_document;
33+
_reflectPropertiesAsAttributes: boolean;
3034

3135
constructor(public _eventManager: EventManager, public _shadowDomStrategy: ShadowDomStrategy,
32-
@Inject(DOCUMENT_TOKEN) document) {
36+
@Inject(DOCUMENT_TOKEN) document,
37+
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
38+
boolean) {
3339
super();
40+
this._reflectPropertiesAsAttributes = reflectPropertiesAsAttributes;
3441
this._document = document;
3542
}
3643

@@ -186,6 +193,11 @@ export class DomRenderer extends Renderer {
186193
setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any): void {
187194
var view = resolveInternalDomView(location.renderView);
188195
view.setElementProperty(location.boundElementIndex, propertyName, propertyValue);
196+
// Reflect the property value as an attribute value with ng-reflect- prefix.
197+
if (this._reflectPropertiesAsAttributes) {
198+
this.setElementAttribute(location, REFLECT_PREFIX + camelCaseToDashCase(propertyName),
199+
propertyValue);
200+
}
189201
}
190202

191203
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string):

modules/angular2/src/test_lib/test_injector.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils
5050
import {ELEMENT_PROBE_CONFIG} from 'angular2/debug';
5151
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
5252
import {RenderCompiler, Renderer} from 'angular2/src/render/api';
53-
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
53+
import {
54+
DomRenderer,
55+
DOCUMENT_TOKEN,
56+
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
57+
} from 'angular2/src/render/dom/dom_renderer';
5458
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
5559

5660
/**
@@ -90,6 +94,7 @@ function _getAppBindings() {
9094
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
9195
DomRenderer,
9296
DefaultDomCompiler,
97+
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
9398
bind(Renderer).toAlias(DomRenderer),
9499
bind(RenderCompiler).toAlias(DefaultDomCompiler),
95100
ProtoViewFactory,

modules/angular2/test/render/dom/dom_renderer_integration_spec.ts

+43
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
1919
import {DomTestbed, TestView, elRef} from './dom_testbed';
2020

2121
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api';
22+
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer';
23+
import {bind} from 'angular2/di';
2224

2325
export function main() {
2426
describe('DomRenderer integration', () => {
@@ -126,6 +128,47 @@ export function main() {
126128
});
127129
}));
128130

131+
132+
it('should NOT reflect property values as attributes if flag is NOT set',
133+
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
134+
tb.compileAll([
135+
someComponent,
136+
new ViewDefinition(
137+
{componentId: 'someComponent', template: '<input [title]="y">', directives: []})
138+
])
139+
.then((protoViewDtos) => {
140+
var rootView = tb.createRootView(protoViewDtos[0]);
141+
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
142+
var el = DOM.childNodes(tb.rootEl)[0];
143+
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
144+
expect((<HTMLInputElement>el).getAttribute('ng-reflect-max-length')).toEqual(null);
145+
async.done();
146+
});
147+
}));
148+
149+
describe('reflection', () => {
150+
beforeEachBindings(() => [bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(true)]);
151+
it('should reflect property values as attributes if flag is set',
152+
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
153+
tb.compileAll([
154+
someComponent,
155+
new ViewDefinition({
156+
componentId: 'someComponent',
157+
template: '<input [title]="y">',
158+
directives: []
159+
})
160+
])
161+
.then((protoViewDtos) => {
162+
var rootView = tb.createRootView(protoViewDtos[0]);
163+
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
164+
var el = DOM.childNodes(tb.rootEl)[0];
165+
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
166+
expect((<HTMLInputElement>el).getAttribute('ng-reflect-max-length')).toEqual('20');
167+
async.done();
168+
});
169+
}));
170+
});
171+
129172
if (DOM.supportsDOMEvents()) {
130173
it('should call actions on the element independent of the compilation',
131174
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {

0 commit comments

Comments
 (0)