Skip to content

Commit

Permalink
feat(core): add ability to reflect DOM properties as attributes
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jeffbcross committed Jul 15, 2015
1 parent 66ec4d1 commit 903ff90
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 5 deletions.
6 changes: 5 additions & 1 deletion modules/angular2/angular2.ts
Expand Up @@ -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';
7 changes: 6 additions & 1 deletion modules/angular2/src/core/application.ts
Expand Up @@ -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';

Expand All @@ -77,6 +81,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
return [
bind(DOCUMENT_TOKEN)
.toValue(DOM.defaultDoc()),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
bind(appComponentTypeToken).toValue(appComponentType),
bind(appComponentRefPromiseToken)
.toFactory(
Expand Down
16 changes: 14 additions & 2 deletions modules/angular2/src/render/dom/dom_renderer.ts
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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):
Expand Down
7 changes: 6 additions & 1 deletion modules/angular2/src/test_lib/test_injector.ts
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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,
Expand Down
46 changes: 46 additions & 0 deletions modules/angular2/test/render/dom/dom_renderer_integration_spec.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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: '<input [title]="y">', 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(<HTMLInputElement>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: '<input [title]="y">',
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(<HTMLInputElement>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) => {
Expand Down

0 comments on commit 903ff90

Please sign in to comment.