Skip to content

Commit

Permalink
feat(dom_renderer): add setBindingDebugInfo method
Browse files Browse the repository at this point in the history
This is used for setting property binding values as attributes
on elements when running in dev mode. This implementation will
also serialize binding information to template placeholder
comment nodes.

Closes angular#5227
  • Loading branch information
jeffbcross committed Nov 20, 2015
1 parent ae58934 commit 17035f5
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 7 deletions.
2 changes: 1 addition & 1 deletion modules/angular2/src/core/linker/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
logBindingUpdate(b: BindingTarget, value: any): void {
if (b.isDirective() || b.isElementProperty()) {
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
this.renderer.setElementAttribute(
this.renderer.setBindingDebugInfo(
elementRef, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
}
}
Expand Down
3 changes: 3 additions & 0 deletions modules/angular2/src/core/render/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ export abstract class Renderer {
abstract setElementAttribute(location: RenderElementRef, attributeName: string,
attributeValue: string);

abstract setBindingDebugInfo(location: RenderElementRef, propertyName: string,
propertyValue: string);

/**
* Sets a (CSS) class on the Element specified via `location`.
*
Expand Down
33 changes: 31 additions & 2 deletions modules/angular2/src/platform/dom/dom_renderer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {Inject, Injectable, OpaqueToken} from 'angular2/src/core/di';
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';

import {StringMapWrapper} from 'angular2/src/facade/collection';
import {
isPresent,
isBlank,
Json,
RegExpWrapper,
CONST_EXPR,
stringify,
Expand Down Expand Up @@ -41,11 +43,13 @@ import {
import {camelCaseToDashCase} from './util';
import {ViewEncapsulation} from 'angular2/src/core/metadata';

// TODO move it once DomAdapter is moved
// TODO move it once DdomAdapter is moved
import {DOM} from 'angular2/src/platform/dom/dom_adapter';

const NAMESPACE_URIS =
CONST_EXPR({'xlink': 'http://www.w3.org/1999/xlink', 'svg': 'http://www.w3.org/2000/svg'});
const TEMPLATE_COMMENT_TEXT = 'template bindings={}';
var TEMPLATE_BINDINGS_EXP = /^template bindings=(.*)$/g;

export abstract class DomRenderer extends Renderer implements NodeFactory<Node> {
abstract registerComponentTemplate(template: RenderComponentTemplate);
Expand Down Expand Up @@ -118,7 +122,7 @@ export abstract class DomRenderer extends Renderer implements NodeFactory<Node>
dehydrateView(viewRef: RenderViewRef) { resolveInternalDomView(viewRef).dehydrate(); }

createTemplateAnchor(attrNameAndValues: string[]): Node {
return this.createElement('script', attrNameAndValues);
return DOM.createComment(TEMPLATE_COMMENT_TEXT);
}
abstract createElement(name: string, attrNameAndValues: string[]): Node;
abstract mergeElement(existing: Node, attrNameAndValues: string[]);
Expand Down Expand Up @@ -146,6 +150,31 @@ export abstract class DomRenderer extends Renderer implements NodeFactory<Node>
}
}

/**
* Used only in debug mode to serialize property changes to comment nodes,
* such as <template> placeholders.
*/
setBindingDebugInfo(location: RenderElementRef, propertyName: string,
propertyValue: string): void {
var view: DefaultRenderView<Node> = resolveInternalDomView(location.renderView);
var element = view.boundElements[location.boundElementIndex];
var dashCasedPropertyName = camelCaseToDashCase(propertyName);
if (DOM.isCommentNode(element)) {
var existingBindings = RegExpWrapper.firstMatch(
TEMPLATE_BINDINGS_EXP, StringWrapper.replaceAll(DOM.getText(element), /\n/g, ''));
var parsedBindings = Json.parse(existingBindings[1]);
if (isPresent(propertyValue)) {
parsedBindings[dashCasedPropertyName] = propertyValue;
} else {
StringMapWrapper.delete(parsedBindings, dashCasedPropertyName);
}
DOM.setText(element, StringWrapper.replace(TEMPLATE_COMMENT_TEXT, '{}',
Json.stringify(parsedBindings)));
} else {
this.setElementAttribute(location, propertyName, propertyValue);
}
}

setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
var view = resolveInternalDomView(location.renderView);
var element = view.boundElements[location.boundElementIndex];
Expand Down
2 changes: 2 additions & 0 deletions modules/angular2/src/web_workers/ui/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export class MessageBasedRenderer {
bind(this._renderer.setElementProperty, this._renderer));
broker.registerMethod("setElementAttribute", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
bind(this._renderer.setElementAttribute, this._renderer));
broker.registerMethod("setBindingDebugInfo", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
bind(this._renderer.setBindingDebugInfo, this._renderer));
broker.registerMethod("setElementClass", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
bind(this._renderer.setElementClass, this._renderer));
broker.registerMethod("setElementStyle", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
Expand Down
11 changes: 11 additions & 0 deletions modules/angular2/src/web_workers/worker/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,17 @@ export class WebWorkerRenderer implements Renderer {
this._messageBroker.runOnService(args, null);
}

setBindingDebugInfo(location: RenderElementRef, propertyName: string,
propertyValue: string): void {
var fnArgs = [
new FnArg(location, WebWorkerElementRef),
new FnArg(propertyName, null),
new FnArg(propertyValue, null)
];
var args = new UiArguments("setBindingDebugInfo", fnArgs);
this._messageBroker.runOnService(args, null);
}

/**
* Sets a class on an element.
*/
Expand Down
23 changes: 20 additions & 3 deletions modules/angular2/test/core/linker/integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ import {ViewContainerRef} from 'angular2/src/core/linker/view_container_ref';
import {ViewRef, ViewRef_} from 'angular2/src/core/linker/view_ref';

import {Compiler} from 'angular2/src/core/linker/compiler';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref';
import {TemplateRef} from 'angular2/src/core/linker/template_ref';

import {DomRenderer} from 'angular2/src/platform/dom/dom_renderer';
Expand Down Expand Up @@ -1594,6 +1594,22 @@ export function main() {
async.done();
});
}));

it('should reflect property values on template comments',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var tpl = '<template [ng-if]="ctxBoolProp"></template>';
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))

.createAsync(MyComp)
.then((fixture) => {
fixture.debugElement.componentInstance.ctxBoolProp = true;
fixture.detectChanges();

expect(DOM.getInnerHTML(fixture.debugElement.nativeElement))
.toContain('"ng\-reflect\-ng\-if"\: "true"');
async.done();
});
}));
});

describe('different proto view storages', () => {
Expand Down Expand Up @@ -1770,6 +1786,7 @@ export function main() {
}));
});


if (DOM.supportsDOMEvents()) {
describe('svg', () => {
it('should support svg elements',
Expand Down Expand Up @@ -1925,8 +1942,8 @@ class PushCmpWithAsyncPipe {
@Injectable()
class MyComp {
ctxProp: string;
ctxNumProp;
ctxBoolProp;
ctxNumProp: number;
ctxBoolProp: boolean;
constructor() {
this.ctxProp = 'initial value';
this.ctxNumProp = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ServiceMessageBrokerFactory_
} from 'angular2/src/web_workers/shared/service_message_broker';
import {WebWorkerEventDispatcher} from 'angular2/src/web_workers/worker/event_dispatcher';
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';


export function main() {
Expand Down Expand Up @@ -114,6 +115,8 @@ export function main() {
var workerRenderProtoViewStore = new RenderProtoViewRefStore(true);
var workerRenderViewStore = new RenderViewWithFragmentsStore(true);
return [
provide(ChangeDetectorGenConfig,
{useValue: new ChangeDetectorGenConfig(true, true, false)}),
provide(RenderProtoViewRefStore, {useValue: workerRenderProtoViewStore}),
provide(RenderViewWithFragmentsStore, {useValue: workerRenderViewStore}),
provide(Renderer,
Expand Down Expand Up @@ -185,6 +188,22 @@ export function main() {
});
}));

it('should update any template comment property/attributes',
inject([TestComponentBuilder, Renderer, AsyncTestCompleter],
(tcb: TestComponentBuilder, renderer: Renderer, async) => {
var tpl = '<template [ng-if]="ctxBoolProp"></template>';
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))

.createAsync(MyComp)
.then((fixture) => {
(<MyComp>fixture.debugElement.componentInstance).ctxBoolProp = true;
fixture.detectChanges();
var el = getRenderElement(fixture.debugElement.elementRef);
expect(DOM.getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
async.done();
});
}));

it('should add and remove fragments',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
Expand Down Expand Up @@ -235,7 +254,7 @@ export function main() {
class MyComp {
ctxProp: string;
ctxNumProp;
ctxBoolProp;
ctxBoolProp: boolean;
constructor() {
this.ctxProp = 'initial value';
this.ctxNumProp = 0;
Expand Down

0 comments on commit 17035f5

Please sign in to comment.