Skip to content

Commit db49d42

Browse files
tboschIgorMinar
authored andcommitted
refactor(compiler): generate less code for bindings to DOM elements
Detailed changes: - remove `UNINITIALIZED`, initialize change detection fields with `undefined`. * we use `view.numberOfChecks === 0` now everywhere as indicator whether we are in the first change detection cycle (previously we used this only in a couple of places). * we keep the initialization itself as change detection get slower without it. - remove passing around `throwOnChange` in various generated calls, and store it on the view as property instead. - change generated code for bindings to DOM elements as follows: Before: ``` var currVal_10 = self.context.bgColor; if (jit_checkBinding15(self.throwOnChange,self._expr_10,currVal_10)) { self.renderer.setElementStyle(self._el_0,'backgroundColor',((self.viewUtils.sanitizer.sanitize(jit_21,currVal_10) == null)? null: self.viewUtils.sanitizer.sanitize(jit_21,currVal_10).toString())); self._expr_10 = currVal_10; } var currVal_11 = jit_inlineInterpolate16(1,' ',self.context.data.value,' '); if (jit_checkBinding15(self.throwOnChange,self._expr_11,currVal_11)) { self.renderer.setText(self._text_1,currVal_11); self._expr_11 = currVal_11; } ```, After: ``` var currVal_10 = self.context.bgColor; jit_checkRenderStyle14(self,self._el_0,'backgroundColor',null,self._expr_10,self._expr_10=currVal_10,false,jit_21); var currVal_11 = jit_inlineInterpolate15(1,' ',self.context.data.value,' '); jit_checkRenderText16(self,self._text_1,self._expr_11,self._expr_11=currVal_11,false); ``` Performance impact: - None seen (checked against internal latency lab) Part of angular#13651
1 parent 8ed92d7 commit db49d42

File tree

23 files changed

+346
-286
lines changed

23 files changed

+346
-286
lines changed

modules/@angular/compiler/src/compiler_util/binding_util.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,14 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
2121
const fieldExpr = createBindFieldExpr(bindingId);
2222
// private is fine here as no child view will reference the cached value...
2323
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
24-
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
25-
.set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)))
26-
.toStmt());
24+
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name).set(o.literal(undefined)).toStmt());
2725
return new CheckBindingField(fieldExpr, bindingId);
2826
}
2927

30-
export function createCheckBindingStmt(
31-
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
32-
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
33-
let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
34-
throwOnChangeVar, fieldExpr, evalResult.currValExpr
35-
]);
36-
if (evalResult.forceUpdate) {
37-
condition = evalResult.forceUpdate.or(condition);
38-
}
39-
return [
40-
...evalResult.stmts, new o.IfStmt(condition, actions.concat([
41-
<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt()
42-
]))
43-
];
44-
}
45-
4628
function createBindFieldExpr(bindingId: string): o.ReadPropExpr {
4729
return o.THIS_EXPR.prop(`_expr_${bindingId}`);
4830
}
31+
32+
export function isFirstViewCheck(view: o.Expression): o.Expression {
33+
return o.not(view.prop('numberOfChecks'));
34+
}

modules/@angular/compiler/src/compiler_util/render_util.ts

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,69 +13,64 @@ import * as o from '../output/output_ast';
1313
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
1414
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
1515

16+
import {isFirstViewCheck} from './binding_util';
17+
import {ConvertPropertyBindingResult} from './expression_converter';
1618
import {createEnumExpression} from './identifier_util';
1719

18-
export function writeToRenderer(
19-
view: o.Expression, boundProp: BoundElementPropertyAst, renderElement: o.Expression,
20-
renderValue: o.Expression, logBindingUpdate: boolean,
20+
export function createCheckRenderBindingStmt(
21+
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
22+
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult,
2123
securityContextExpression?: o.Expression): o.Statement[] {
22-
const updateStmts: o.Statement[] = [];
23-
const renderer = view.prop('renderer');
24-
renderValue = sanitizedValue(view, boundProp, renderValue, securityContextExpression);
24+
const checkStmts: o.Statement[] = [...evalResult.stmts];
25+
const securityContext = calcSecurityContext(boundProp, securityContextExpression);
2526
switch (boundProp.type) {
2627
case PropertyBindingType.Property:
27-
if (logBindingUpdate) {
28-
updateStmts.push(
29-
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo))
30-
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
31-
.toStmt());
32-
}
33-
updateStmts.push(
34-
renderer
35-
.callMethod(
36-
'setElementProperty', [renderElement, o.literal(boundProp.name), renderValue])
37-
.toStmt());
28+
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderProperty))
29+
.callFn([
30+
view, renderElement, o.literal(boundProp.name), oldValue,
31+
oldValue.set(evalResult.currValExpr),
32+
evalResult.forceUpdate || o.literal(false), securityContext
33+
])
34+
.toStmt());
3835
break;
3936
case PropertyBindingType.Attribute:
40-
renderValue =
41-
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
42-
updateStmts.push(
43-
renderer
44-
.callMethod(
45-
'setElementAttribute', [renderElement, o.literal(boundProp.name), renderValue])
46-
.toStmt());
37+
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderAttribute))
38+
.callFn([
39+
view, renderElement, o.literal(boundProp.name), oldValue,
40+
oldValue.set(evalResult.currValExpr),
41+
evalResult.forceUpdate || o.literal(false), securityContext
42+
])
43+
.toStmt());
4744
break;
4845
case PropertyBindingType.Class:
49-
updateStmts.push(
50-
renderer
51-
.callMethod(
52-
'setElementClass', [renderElement, o.literal(boundProp.name), renderValue])
46+
checkStmts.push(
47+
o.importExpr(createIdentifier(Identifiers.checkRenderClass))
48+
.callFn([
49+
view, renderElement, o.literal(boundProp.name), oldValue,
50+
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false)
51+
])
5352
.toStmt());
5453
break;
5554
case PropertyBindingType.Style:
56-
let strValue: o.Expression = renderValue.callMethod('toString', []);
57-
if (isPresent(boundProp.unit)) {
58-
strValue = strValue.plus(o.literal(boundProp.unit));
59-
}
60-
61-
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
62-
updateStmts.push(
63-
renderer
64-
.callMethod(
65-
'setElementStyle', [renderElement, o.literal(boundProp.name), renderValue])
55+
checkStmts.push(
56+
o.importExpr(createIdentifier(Identifiers.checkRenderStyle))
57+
.callFn([
58+
view, renderElement, o.literal(boundProp.name), o.literal(boundProp.unit), oldValue,
59+
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false),
60+
securityContext
61+
])
6662
.toStmt());
6763
break;
6864
case PropertyBindingType.Animation:
6965
throw new Error('Illegal state: Should not come here!');
7066
}
71-
return updateStmts;
67+
return checkStmts;
7268
}
7369

74-
function sanitizedValue(
75-
view: o.Expression, boundProp: BoundElementPropertyAst, renderValue: o.Expression,
76-
securityContextExpression?: o.Expression): o.Expression {
70+
function calcSecurityContext(
71+
boundProp: BoundElementPropertyAst, securityContextExpression?: o.Expression): o.Expression {
7772
if (boundProp.securityContext === SecurityContext.NONE) {
78-
return renderValue; // No sanitization needed.
73+
return o.NULL_EXPR; // No sanitization needed.
7974
}
8075
if (!boundProp.needsRuntimeSecurityContext) {
8176
securityContextExpression =
@@ -84,15 +79,13 @@ function sanitizedValue(
8479
if (!securityContextExpression) {
8580
throw new Error(`internal error, no SecurityContext given ${boundProp.name}`);
8681
}
87-
const ctx = view.prop('viewUtils').prop('sanitizer');
88-
const args = [securityContextExpression, renderValue];
89-
return ctx.callMethod('sanitize', args);
82+
return securityContextExpression;
9083
}
9184

92-
export function triggerAnimation(
85+
export function createCheckAnimationBindingStmts(
9386
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
9487
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
95-
renderValue: o.Expression, lastRenderValue: o.Expression) {
88+
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) {
9689
const detachStmts: o.Statement[] = [];
9790
const updateStmts: o.Statement[] = [];
9891

@@ -104,22 +97,21 @@ export function triggerAnimation(
10497
// it's important to normalize the void value as `void` explicitly
10598
// so that the styles data can be obtained from the stringmap
10699
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
107-
const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED));
108100
const animationTransitionVar = o.variable('animationTransition_' + animationName);
109101

110102
updateStmts.push(
111103
animationTransitionVar
112104
.set(animationFnExpr.callFn([
113-
view, renderElement,
114-
lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue),
115-
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
105+
view, renderElement, isFirstViewCheck(view).conditional(emptyStateValue, oldValue),
106+
evalResult.currValExpr
116107
]))
117108
.toDeclStmt());
109+
updateStmts.push(oldValue.set(evalResult.currValExpr).toStmt());
118110

119-
detachStmts.push(
120-
animationTransitionVar
121-
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
122-
.toDeclStmt());
111+
detachStmts.push(animationTransitionVar
112+
.set(animationFnExpr.callFn(
113+
[view, renderElement, evalResult.currValExpr, emptyStateValue]))
114+
.toDeclStmt());
123115

124116
const registerStmts: o.Statement[] = [];
125117
const animationStartMethodExists = boundOutputs.find(
@@ -151,5 +143,14 @@ export function triggerAnimation(
151143
updateStmts.push(...registerStmts);
152144
detachStmts.push(...registerStmts);
153145

154-
return {updateStmts, detachStmts};
146+
const checkUpdateStmts: o.Statement[] = [
147+
...evalResult.stmts,
148+
new o.IfStmt(
149+
o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
150+
view, oldValue, evalResult.currValExpr, evalResult.forceUpdate || o.literal(false)
151+
]),
152+
updateStmts)
153+
];
154+
const checkDetachStmts: o.Statement[] = [...evalResult.stmts, ...detachStmts];
155+
return {checkUpdateStmts, checkDetachStmts};
155156
}

modules/@angular/compiler/src/directive_wrapper_compiler.ts

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
*/
88

99
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata';
10-
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
10+
import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util';
1111
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
12-
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
12+
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util';
1313
import {CompilerConfig} from './config';
1414
import {Parser} from './expression_parser/parser';
1515
import {Identifiers, createIdentifier} from './identifiers';
@@ -32,8 +32,8 @@ const CHANGES_FIELD_NAME = '_changes';
3232
const CHANGED_FIELD_NAME = '_changed';
3333
const EVENT_HANDLER_FIELD_NAME = '_eventHandler';
3434

35+
const CHANGE_VAR = o.variable('change');
3536
const CURR_VALUE_VAR = o.variable('currValue');
36-
const THROW_ON_CHANGE_VAR = o.variable('throwOnChange');
3737
const FORCE_UPDATE_VAR = o.variable('forceUpdate');
3838
const VIEW_VAR = o.variable('view');
3939
const COMPONENT_VIEW_VAR = o.variable('componentView');
@@ -130,8 +130,9 @@ class DirectiveWrapperBuilder implements ClassBuilder {
130130
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
131131
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE, [o.StmtModifier.Private]),
132132
];
133-
const ctorStmts: o.Statement[] =
134-
[o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()];
133+
const ctorStmts: o.Statement[] = [
134+
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt(),
135+
];
135136
if (this.genChanges) {
136137
fields.push(new o.ClassField(
137138
CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE), [o.StmtModifier.Private]));
@@ -180,14 +181,14 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
180181

181182
if (builder.ngOnInit) {
182183
lifecycleStmts.push(new o.IfStmt(
183-
VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)),
184+
isFirstViewCheck(VIEW_VAR),
184185
[o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()]));
185186
}
186187
if (builder.ngDoCheck) {
187188
lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt());
188189
}
189190
if (lifecycleStmts.length > 0) {
190-
stmts.push(new o.IfStmt(o.not(THROW_ON_CHANGE_VAR), lifecycleStmts));
191+
stmts.push(new o.IfStmt(o.not(VIEW_VAR.prop('throwOnChange')), lifecycleStmts));
191192
}
192193
stmts.push(new o.ReturnStatement(changedVar));
193194

@@ -197,7 +198,6 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
197198
new o.FnParam(
198199
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
199200
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
200-
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
201201
],
202202
stmts, o.BOOL_TYPE));
203203
}
@@ -207,24 +207,35 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
207207
const onChangeStatements: o.Statement[] = [
208208
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(),
209209
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(),
210+
field.expression.set(CURR_VALUE_VAR).toStmt()
210211
];
212+
let methodBody: o.Statement[];
211213
if (builder.genChanges) {
212-
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
213-
.key(o.literal(input))
214-
.set(o.importExpr(createIdentifier(Identifiers.SimpleChange))
215-
.instantiate([field.expression, CURR_VALUE_VAR]))
216-
.toStmt());
214+
onChangeStatements.push(
215+
o.THIS_EXPR.prop(CHANGES_FIELD_NAME).key(o.literal(input)).set(CHANGE_VAR).toStmt());
216+
methodBody = [
217+
CHANGE_VAR
218+
.set(o.importExpr(createIdentifier(Identifiers.checkBindingChange)).callFn([
219+
VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR
220+
]))
221+
.toDeclStmt(),
222+
new o.IfStmt(CHANGE_VAR, onChangeStatements)
223+
];
224+
} else {
225+
methodBody = [new o.IfStmt(
226+
o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
227+
VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR
228+
]),
229+
onChangeStatements)];
217230
}
218231

219-
const methodBody: o.Statement[] = createCheckBindingStmt(
220-
{currValExpr: CURR_VALUE_VAR, forceUpdate: FORCE_UPDATE_VAR, stmts: []}, field.expression,
221-
THROW_ON_CHANGE_VAR, onChangeStatements);
222232
builder.methods.push(new o.ClassMethod(
223233
`check_${input}`,
224234
[
235+
new o.FnParam(
236+
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
225237
new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE),
226-
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
227-
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE),
238+
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE)
228239
],
229240
methodBody));
230241
}
@@ -240,7 +251,6 @@ function addCheckHostMethod(
240251
COMPONENT_VIEW_VAR.name,
241252
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
242253
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
243-
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
244254
];
245255
hostProps.forEach((hostProp, hostPropIdx) => {
246256
const field = createCheckBindingField(builder);
@@ -255,23 +265,18 @@ function addCheckHostMethod(
255265
methodParams.push(new o.FnParam(
256266
securityContextExpr.name, o.importType(createIdentifier(Identifiers.SecurityContext))));
257267
}
258-
let checkBindingStmts: o.Statement[];
259268
if (hostProp.isAnimation) {
260-
const {updateStmts, detachStmts} = triggerAnimation(
269+
const {checkUpdateStmts, checkDetachStmts} = createCheckAnimationBindingStmts(
261270
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp, hostEvents,
262271
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
263272
.or(o.importExpr(createIdentifier(Identifiers.noop))),
264-
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
265-
checkBindingStmts = updateStmts;
266-
builder.detachStmts.push(...detachStmts);
273+
RENDER_EL_VAR, field.expression, evalResult);
274+
builder.detachStmts.push(...checkDetachStmts);
275+
stmts.push(...checkUpdateStmts);
267276
} else {
268-
checkBindingStmts = writeToRenderer(
269-
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
270-
builder.compilerConfig.logBindingUpdate, securityContextExpr);
277+
stmts.push(...createCheckRenderBindingStmt(
278+
VIEW_VAR, RENDER_EL_VAR, hostProp, field.expression, evalResult, securityContextExpr));
271279
}
272-
273-
stmts.push(...createCheckBindingStmt(
274-
evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts));
275280
});
276281
builder.methods.push(new o.ClassMethod('checkHost', methodParams, stmts));
277282
}
@@ -381,20 +386,19 @@ export class DirectiveWrapperExpressions {
381386
return dirWrapper.prop(CONTEXT_FIELD_NAME);
382387
}
383388

384-
static ngDoCheck(
385-
dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression,
386-
throwOnChange: o.Expression): o.Expression {
387-
return dirWrapper.callMethod('ngDoCheck', [view, renderElement, throwOnChange]);
389+
static ngDoCheck(dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression, ):
390+
o.Expression {
391+
return dirWrapper.callMethod('ngDoCheck', [view, renderElement]);
388392
}
389393
static checkHost(
390394
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
391-
componentView: o.Expression, renderElement: o.Expression, throwOnChange: o.Expression,
395+
componentView: o.Expression, renderElement: o.Expression,
392396
runtimeSecurityContexts: o.Expression[]): o.Statement[] {
393397
if (hostProps.length) {
394398
return [dirWrapper
395399
.callMethod(
396-
'checkHost', [view, componentView, renderElement, throwOnChange].concat(
397-
runtimeSecurityContexts))
400+
'checkHost',
401+
[view, componentView, renderElement].concat(runtimeSecurityContexts))
398402
.toStmt()];
399403
} else {
400404
return [];

0 commit comments

Comments
 (0)