Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Commit 79399e1

Browse files
jeffbcrossvsavkin
authored andcommitted
feat(dom_renderer): add setBindingDebugInfo method
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 #5227
1 parent fe1dd77 commit 79399e1

File tree

7 files changed

+88
-7
lines changed

7 files changed

+88
-7
lines changed

modules/angular2/src/core/linker/view.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
173173
logBindingUpdate(b: BindingTarget, value: any): void {
174174
if (b.isDirective() || b.isElementProperty()) {
175175
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
176-
this.renderer.setElementAttribute(
176+
this.renderer.setBindingDebugInfo(
177177
elementRef, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
178178
}
179179
}

modules/angular2/src/core/render/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ export abstract class Renderer {
300300
abstract setElementAttribute(location: RenderElementRef, attributeName: string,
301301
attributeValue: string);
302302

303+
abstract setBindingDebugInfo(location: RenderElementRef, propertyName: string,
304+
propertyValue: string);
305+
303306
/**
304307
* Sets a (CSS) class on the Element specified via `location`.
305308
*

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {Inject, Injectable, OpaqueToken} from 'angular2/src/core/di';
22
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
33

4+
import {StringMapWrapper} from 'angular2/src/facade/collection';
45
import {
56
isPresent,
67
isBlank,
8+
Json,
79
RegExpWrapper,
810
CONST_EXPR,
911
stringify,
@@ -41,11 +43,13 @@ import {
4143
} from 'angular2/src/core/render/view';
4244
import {ViewEncapsulation} from 'angular2/src/core/metadata';
4345

44-
// TODO move it once DomAdapter is moved
46+
// TODO move it once DdomAdapter is moved
4547
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
4648

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

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

120124
createTemplateAnchor(attrNameAndValues: string[]): Node {
121-
return this.createElement('script', attrNameAndValues);
125+
return DOM.createComment(TEMPLATE_COMMENT_TEXT);
122126
}
123127
abstract createElement(name: string, attrNameAndValues: string[]): Node;
124128
abstract mergeElement(existing: Node, attrNameAndValues: string[]);
@@ -145,6 +149,31 @@ export abstract class DomRenderer extends Renderer implements NodeFactory<Node>
145149
}
146150
}
147151

152+
/**
153+
* Used only in debug mode to serialize property changes to comment nodes,
154+
* such as <template> placeholders.
155+
*/
156+
setBindingDebugInfo(location: RenderElementRef, propertyName: string,
157+
propertyValue: string): void {
158+
var view: DefaultRenderView<Node> = resolveInternalDomView(location.renderView);
159+
var element = view.boundElements[location.boundElementIndex];
160+
var dashCasedPropertyName = camelCaseToDashCase(propertyName);
161+
if (DOM.isCommentNode(element)) {
162+
var existingBindings = RegExpWrapper.firstMatch(
163+
TEMPLATE_BINDINGS_EXP, StringWrapper.replaceAll(DOM.getText(element), /\n/g, ''));
164+
var parsedBindings = Json.parse(existingBindings[1]);
165+
if (isPresent(propertyValue)) {
166+
parsedBindings[dashCasedPropertyName] = propertyValue;
167+
} else {
168+
StringMapWrapper.delete(parsedBindings, dashCasedPropertyName);
169+
}
170+
DOM.setText(element, StringWrapper.replace(TEMPLATE_COMMENT_TEXT, '{}',
171+
Json.stringify(parsedBindings)));
172+
} else {
173+
this.setElementAttribute(location, propertyName, propertyValue);
174+
}
175+
}
176+
148177
setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
149178
var view = resolveInternalDomView(location.renderView);
150179
var element = view.boundElements[location.boundElementIndex];

modules/angular2/src/web_workers/ui/renderer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export class MessageBasedRenderer {
5858
bind(this._renderer.setElementProperty, this._renderer));
5959
broker.registerMethod("setElementAttribute", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
6060
bind(this._renderer.setElementAttribute, this._renderer));
61+
broker.registerMethod("setBindingDebugInfo", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
62+
bind(this._renderer.setBindingDebugInfo, this._renderer));
6163
broker.registerMethod("setElementClass", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
6264
bind(this._renderer.setElementClass, this._renderer));
6365
broker.registerMethod("setElementStyle", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],

modules/angular2/src/web_workers/worker/renderer.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,17 @@ export class WebWorkerRenderer implements Renderer {
196196
this._messageBroker.runOnService(args, null);
197197
}
198198

199+
setBindingDebugInfo(location: RenderElementRef, propertyName: string,
200+
propertyValue: string): void {
201+
var fnArgs = [
202+
new FnArg(location, WebWorkerElementRef),
203+
new FnArg(propertyName, null),
204+
new FnArg(propertyValue, null)
205+
];
206+
var args = new UiArguments("setBindingDebugInfo", fnArgs);
207+
this._messageBroker.runOnService(args, null);
208+
}
209+
199210
/**
200211
* Sets a class on an element.
201212
*/

modules/angular2/test/core/linker/integration_spec.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ import {ViewContainerRef} from 'angular2/src/core/linker/view_container_ref';
8989
import {ViewRef, ViewRef_} from 'angular2/src/core/linker/view_ref';
9090

9191
import {Compiler} from 'angular2/src/core/linker/compiler';
92-
import {ElementRef} from 'angular2/src/core/linker/element_ref';
92+
import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref';
9393
import {TemplateRef} from 'angular2/src/core/linker/template_ref';
9494

9595
import {DomRenderer} from 'angular2/src/platform/dom/dom_renderer';
@@ -1597,6 +1597,22 @@ export function main() {
15971597
async.done();
15981598
});
15991599
}));
1600+
1601+
it('should reflect property values on template comments',
1602+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
1603+
var tpl = '<template [ng-if]="ctxBoolProp"></template>';
1604+
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))
1605+
1606+
.createAsync(MyComp)
1607+
.then((fixture) => {
1608+
fixture.debugElement.componentInstance.ctxBoolProp = true;
1609+
fixture.detectChanges();
1610+
1611+
expect(DOM.getInnerHTML(fixture.debugElement.nativeElement))
1612+
.toContain('"ng\-reflect\-ng\-if"\: "true"');
1613+
async.done();
1614+
});
1615+
}));
16001616
});
16011617

16021618
describe('different proto view storages', () => {
@@ -1773,6 +1789,7 @@ export function main() {
17731789
}));
17741790
});
17751791

1792+
17761793
if (DOM.supportsDOMEvents()) {
17771794
describe('svg', () => {
17781795
it('should support svg elements',
@@ -1928,8 +1945,8 @@ class PushCmpWithAsyncPipe {
19281945
@Injectable()
19291946
class MyComp {
19301947
ctxProp: string;
1931-
ctxNumProp;
1932-
ctxBoolProp;
1948+
ctxNumProp: number;
1949+
ctxBoolProp: boolean;
19331950
constructor() {
19341951
this.ctxProp = 'initial value';
19351952
this.ctxNumProp = 0;

modules/angular2/test/web_workers/worker/renderer_integration_spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {
5656
ServiceMessageBrokerFactory_
5757
} from 'angular2/src/web_workers/shared/service_message_broker';
5858
import {WebWorkerEventDispatcher} from 'angular2/src/web_workers/worker/event_dispatcher';
59+
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
5960

6061

6162
export function main() {
@@ -112,6 +113,8 @@ export function main() {
112113
var workerRenderProtoViewStore = new RenderProtoViewRefStore(true);
113114
var workerRenderViewStore = new RenderViewWithFragmentsStore(true);
114115
return [
116+
provide(ChangeDetectorGenConfig,
117+
{useValue: new ChangeDetectorGenConfig(true, true, false)}),
115118
provide(RenderProtoViewRefStore, {useValue: workerRenderProtoViewStore}),
116119
provide(RenderViewWithFragmentsStore, {useValue: workerRenderViewStore}),
117120
provide(Renderer,
@@ -183,6 +186,22 @@ export function main() {
183186
});
184187
}));
185188

189+
it('should update any template comment property/attributes',
190+
inject([TestComponentBuilder, Renderer, AsyncTestCompleter],
191+
(tcb: TestComponentBuilder, renderer: Renderer, async) => {
192+
var tpl = '<template [ng-if]="ctxBoolProp"></template>';
193+
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))
194+
195+
.createAsync(MyComp)
196+
.then((fixture) => {
197+
(<MyComp>fixture.debugElement.componentInstance).ctxBoolProp = true;
198+
fixture.detectChanges();
199+
var el = getRenderElement(fixture.debugElement.elementRef);
200+
expect(DOM.getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
201+
async.done();
202+
});
203+
}));
204+
186205
it('should add and remove fragments',
187206
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
188207
tcb.overrideView(MyComp, new ViewMetadata({
@@ -232,7 +251,7 @@ export function main() {
232251
class MyComp {
233252
ctxProp: string;
234253
ctxNumProp;
235-
ctxBoolProp;
254+
ctxBoolProp: boolean;
236255
constructor() {
237256
this.ctxProp = 'initial value';
238257
this.ctxNumProp = 0;

0 commit comments

Comments
 (0)