Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
refactor(compiler): extract save/restore view logic to separate phase
Saving and restoring the view is significant enough that it makes sense to handle it independently. This makes for easier reasoning about how view save/restore works.
Co-authored-by: Alex Rickabaugh <[email protected]>
  • Loading branch information
dylhunn and alxhub committed May 2, 2023
commit a0093d2917996a03f02c216bc33f0ec42f598d91
2 changes: 2 additions & 0 deletions packages/compiler/src/template/pipeline/src/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ import {phaseVariableOptimization} from './phases/variable_optimization';
import {phaseChaining} from './phases/chaining';
import {phaseMergeNextContext} from './phases/next_context_merging';
import {phaseNgContainer} from './phases/ng_container';
import {phaseSaveRestoreView} from './phases/save_restore_view';

/**
* Run all transformation phases in the correct order against a `ComponentCompilation`. After this
* processing, the compilation should be in a state where it can be emitted via `emitTemplateFn`.s
*/
export function transformTemplate(cpl: ComponentCompilation): void {
phaseGenerateVariables(cpl);
phaseSaveRestoreView(cpl);
phaseResolveNames(cpl);
phaseResolveContexts(cpl);
phaseLocalRefs(cpl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,11 @@ function recursivelyProcessView(view: ViewCompilation, parentScope: Scope|null):
// Extract a `Scope` from this view.
const scope = getScopeForView(view, parentScope);

// Start the view creation block with an operation to save the current view context. This may be
// used to restore the view context in any listeners that may be present.
view.create.prepend([
ir.createVariableOp<ir.CreateOp>(
view.tpl.allocateXrefId(), scope.savedViewVariable, new ir.GetCurrentViewExpr()),
]);
// Embedded views require an operation to save/restore the view context.
if (view.parent !== null) {
// Start the view creation block with an operation to save the current view context. This may be
// used to restore the view context in any listeners that may be present.
}

for (const op of view.create) {
switch (op.kind) {
Expand All @@ -54,26 +53,8 @@ function recursivelyProcessView(view: ViewCompilation, parentScope: Scope|null):
recursivelyProcessView(view.tpl.views.get(op.xref)!, scope);
break;
case ir.OpKind.Listener:
// Listeners get a preamble which starts with a call to restore the view.
const preambleOps = [
ir.createVariableOp<ir.UpdateOp>(
view.tpl.allocateXrefId(), scope.viewContextVariable,
new ir.RestoreViewExpr(view.xref)),
// And includes all variables available to this view.
...generateVariablesInScopeForView(view, scope)
];

op.handlerOps.prepend(preambleOps);

// The "restore view" operation in listeners requires a call to `resetView` to reset the
// context prior to returning from the listener operation. Find any `return` statements in
// the listener body and wrap them in a call to reset the view.
for (const handlerOp of op.handlerOps) {
if (handlerOp.kind === ir.OpKind.Statement &&
handlerOp.statement instanceof o.ReturnStatement) {
handlerOp.statement.value = new ir.ResetViewExpr(handlerOp.statement.value);
}
}
// Prepend variables to listener handler functions.
op.handlerOps.prepend(generateVariablesInScopeForView(view, scope));
break;
}
}
Expand All @@ -94,8 +75,6 @@ interface Scope {

viewContextVariable: ir.SemanticVariable;

savedViewVariable: ir.SemanticVariable;

contextVariables: Map<string, ir.SemanticVariable>;

/**
Expand Down Expand Up @@ -148,11 +127,6 @@ function getScopeForView(view: ViewCompilation, parent: Scope|null): Scope {
name: null,
view: view.xref,
},
savedViewVariable: {
kind: ir.SemanticVariableKind.SavedView,
name: null,
view: view.xref,
},
contextVariables: new Map<string, ir.SemanticVariable>(),
references: [],
parent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import * as o from '../../../../output/output_ast';
import * as ir from '../../ir';
import type {ComponentCompilation} from '../compilation';

export function phaseSaveRestoreView(cpl: ComponentCompilation): void {
for (const view of cpl.views.values()) {
if (view === cpl.root) {
// Save/restore operations are not necessary for the root view.
continue;
}

view.create.prepend([
ir.createVariableOp<ir.CreateOp>(
view.tpl.allocateXrefId(), {
kind: ir.SemanticVariableKind.SavedView,
name: null,
view: view.xref,
},
new ir.GetCurrentViewExpr()),
]);

for (const op of view.create) {
if (op.kind !== ir.OpKind.Listener) {
continue;
}

op.handlerOps.prepend([
ir.createVariableOp<ir.UpdateOp>(
view.tpl.allocateXrefId(), {
kind: ir.SemanticVariableKind.Context,
name: null,
view: view.xref,
},
new ir.RestoreViewExpr(view.xref)),
]);

// The "restore view" operation in listeners requires a call to `resetView` to reset the
// context prior to returning from the listener operation. Find any `return` statements in
// the listener body and wrap them in a call to reset the view.
for (const handlerOp of op.handlerOps) {
if (handlerOp.kind === ir.OpKind.Statement &&
handlerOp.statement instanceof o.ReturnStatement) {
handlerOp.statement.value = new ir.ResetViewExpr(handlerOp.statement.value);
}
}
}
}
}