Skip to content

Commit 1388c17

Browse files
JoostKatscott
authored andcommitted
perf(compiler-cli): don't emit template guards when child scope is empty (angular#38418)
For a template that contains for example `<span *ngIf="first"></span>` there's no need to render the `NgIf` guard expression, as the child scope does not have any type-checking statements, so any narrowing effect of the guard is not applicable. This seems like a minor improvement, however it reduces the number of flow-node antecedents that TypeScript needs to keep into account for such cases, resulting in an overall reduction of type-checking time. PR Close angular#38418
1 parent fb8f4b4 commit 1388c17

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,19 @@ class TcbTemplateBodyOp extends TcbOp {
304304
// children, as well as tracks bindings within the template.
305305
const tmplScope = Scope.forNodes(this.tcb, this.scope, this.template, guard);
306306

307-
// Render the template's `Scope` into a block.
308-
let tmplBlock: ts.Statement = ts.createBlock(tmplScope.render());
307+
// Render the template's `Scope` into its statements.
308+
const statements = tmplScope.render();
309+
if (statements.length === 0) {
310+
// As an optimization, don't generate the scope's block if it has no statements. This is
311+
// beneficial for templates that contain for example `<span *ngIf="first"></span>`, in which
312+
// case there's no need to render the `NgIf` guard expression. This seems like a minor
313+
// improvement, however it reduces the number of flow-node antecedents that TypeScript needs
314+
// to keep into account for such cases, resulting in an overall reduction of
315+
// type-checking time.
316+
return null;
317+
}
318+
319+
let tmplBlock: ts.Statement = ts.createBlock(statements);
309320
if (guard !== null) {
310321
// The scope has a guard that needs to be applied, so wrap the template block into an `if`
311322
// statement containing the guard expression.

packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ describe('type check blocks', () => {
585585
type: 'invocation',
586586
}]
587587
}];
588-
const TEMPLATE = `<div *ngIf="person"></div>`;
588+
const TEMPLATE = `<div *ngIf="person">{{person.name}}</div>`;
589589
const block = tcb(TEMPLATE, DIRECTIVES);
590590
expect(block).toContain('if (NgIf.ngTemplateGuard_ngIf(_t1, ((ctx).person)))');
591591
});
@@ -601,10 +601,26 @@ describe('type check blocks', () => {
601601
type: 'binding',
602602
}]
603603
}];
604-
const TEMPLATE = `<div *ngIf="person !== null"></div>`;
604+
const TEMPLATE = `<div *ngIf="person !== null">{{person.name}}</div>`;
605605
const block = tcb(TEMPLATE, DIRECTIVES);
606606
expect(block).toContain('if ((((ctx).person)) !== (null))');
607607
});
608+
609+
it('should not emit guards when the child scope is empty', () => {
610+
const DIRECTIVES: TestDeclaration[] = [{
611+
type: 'directive',
612+
name: 'NgIf',
613+
selector: '[ngIf]',
614+
inputs: {'ngIf': 'ngIf'},
615+
ngTemplateGuards: [{
616+
inputName: 'ngIf',
617+
type: 'invocation',
618+
}]
619+
}];
620+
const TEMPLATE = `<div *ngIf="person">static</div>`;
621+
const block = tcb(TEMPLATE, DIRECTIVES);
622+
expect(block).not.toContain('NgIf.ngTemplateGuard_ngIf');
623+
});
608624
});
609625

610626
describe('outputs', () => {
@@ -681,7 +697,7 @@ describe('type check blocks', () => {
681697
};
682698

683699
describe('config.applyTemplateContextGuards', () => {
684-
const TEMPLATE = `<div *dir></div>`;
700+
const TEMPLATE = `<div *dir>{{ value }}</div>`;
685701
const GUARD_APPLIED = 'if (Dir.ngTemplateContextGuard(';
686702

687703
it('should apply template context guards when enabled', () => {

0 commit comments

Comments
 (0)