@angular/compiler 17.2.0-next.1 → 17.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/esm2022/src/compiler_util/expression_converter.mjs +98 -25
  2. package/esm2022/src/constant_pool.mjs +14 -4
  3. package/esm2022/src/expression_parser/ast.mjs +1 -3
  4. package/esm2022/src/expression_parser/parser.mjs +8 -27
  5. package/esm2022/src/render3/partial/class_metadata.mjs +1 -1
  6. package/esm2022/src/render3/partial/directive.mjs +1 -1
  7. package/esm2022/src/render3/partial/factory.mjs +1 -1
  8. package/esm2022/src/render3/partial/injectable.mjs +1 -1
  9. package/esm2022/src/render3/partial/injector.mjs +1 -1
  10. package/esm2022/src/render3/partial/ng_module.mjs +1 -1
  11. package/esm2022/src/render3/partial/pipe.mjs +1 -1
  12. package/esm2022/src/render3/r3_class_metadata_compiler.mjs +5 -3
  13. package/esm2022/src/render3/r3_identifiers.mjs +6 -1
  14. package/esm2022/src/render3/r3_template_transform.mjs +2 -2
  15. package/esm2022/src/render3/view/api.mjs +1 -1
  16. package/esm2022/src/render3/view/compiler.mjs +3 -3
  17. package/esm2022/src/render3/view/template.mjs +18 -8
  18. package/esm2022/src/render3/view/util.mjs +3 -1
  19. package/esm2022/src/template/pipeline/ir/src/enums.mjs +27 -11
  20. package/esm2022/src/template/pipeline/ir/src/expression.mjs +32 -1
  21. package/esm2022/src/template/pipeline/ir/src/ops/create.mjs +19 -1
  22. package/esm2022/src/template/pipeline/ir/src/ops/update.mjs +22 -1
  23. package/esm2022/src/template/pipeline/src/compilation.mjs +2 -2
  24. package/esm2022/src/template/pipeline/src/emit.mjs +3 -1
  25. package/esm2022/src/template/pipeline/src/ingest.mjs +54 -13
  26. package/esm2022/src/template/pipeline/src/instruction.mjs +14 -1
  27. package/esm2022/src/template/pipeline/src/phases/attribute_extraction.mjs +15 -1
  28. package/esm2022/src/template/pipeline/src/phases/binding_specialization.mjs +11 -1
  29. package/esm2022/src/template/pipeline/src/phases/chaining.mjs +3 -1
  30. package/esm2022/src/template/pipeline/src/phases/const_collection.mjs +12 -5
  31. package/esm2022/src/template/pipeline/src/phases/create_defer_deps_fns.mjs +4 -2
  32. package/esm2022/src/template/pipeline/src/phases/generate_variables.mjs +2 -1
  33. package/esm2022/src/template/pipeline/src/phases/naming.mjs +14 -2
  34. package/esm2022/src/template/pipeline/src/phases/next_context_merging.mjs +2 -2
  35. package/esm2022/src/template/pipeline/src/phases/ordering.mjs +14 -5
  36. package/esm2022/src/template/pipeline/src/phases/reify.mjs +9 -1
  37. package/esm2022/src/template/pipeline/src/phases/resolve_contexts.mjs +2 -1
  38. package/esm2022/src/template/pipeline/src/phases/resolve_dollar_event.mjs +6 -3
  39. package/esm2022/src/template/pipeline/src/phases/resolve_names.mjs +3 -2
  40. package/esm2022/src/template/pipeline/src/phases/save_restore_view.mjs +2 -2
  41. package/esm2022/src/template/pipeline/src/phases/temporary_variables.mjs +2 -2
  42. package/esm2022/src/template/pipeline/src/phases/transform_two_way_binding_set.mjs +79 -0
  43. package/esm2022/src/template/pipeline/src/phases/var_counting.mjs +4 -1
  44. package/esm2022/src/template/pipeline/src/phases/variable_optimization.mjs +3 -3
  45. package/esm2022/src/template_parser/binding_parser.mjs +35 -8
  46. package/esm2022/src/version.mjs +1 -1
  47. package/fesm2022/compiler.mjs +511 -122
  48. package/fesm2022/compiler.mjs.map +1 -1
  49. package/index.d.ts +40 -219
  50. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v17.2.0-next.1
2
+ * @license Angular v17.2.0-rc.1
3
3
  * (c) 2010-2022 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
@@ -2236,6 +2236,13 @@ class ConstantPool {
2236
2236
  this.literals = new Map();
2237
2237
  this.literalFactories = new Map();
2238
2238
  this.sharedConstants = new Map();
2239
+ /**
2240
+ * Constant pool also tracks claimed names from {@link uniqueName}.
2241
+ * This is useful to avoid collisions if variables are intended to be
2242
+ * named a certain way- but may conflict. We wouldn't want to always suffix
2243
+ * them with unique numbers.
2244
+ */
2245
+ this._claimedNames = new Map();
2239
2246
  this.nextNameIndex = 0;
2240
2247
  }
2241
2248
  getConstLiteral(literal, forceShared) {
@@ -2358,14 +2365,17 @@ class ConstantPool {
2358
2365
  return { literalFactory, literalFactoryArguments };
2359
2366
  }
2360
2367
  /**
2361
- * Produce a unique name.
2368
+ * Produce a unique name in the context of this pool.
2362
2369
  *
2363
2370
  * The name might be unique among different prefixes if any of the prefixes end in
2364
2371
  * a digit so the prefix should be a constant string (not based on user input) and
2365
2372
  * must not end in a digit.
2366
2373
  */
2367
- uniqueName(prefix) {
2368
- return `${prefix}${this.nextNameIndex++}`;
2374
+ uniqueName(name, alwaysIncludeSuffix = true) {
2375
+ const count = this._claimedNames.get(name) ?? 0;
2376
+ const result = count === 0 && !alwaysIncludeSuffix ? `${name}` : `${name}${count}`;
2377
+ this._claimedNames.set(name, count + 1);
2378
+ return result;
2369
2379
  }
2370
2380
  freshName() {
2371
2381
  return this.uniqueName(CONSTANT_PREFIX);
@@ -2632,6 +2642,10 @@ class Identifiers {
2632
2642
  static { this.viewQuerySignal = { name: 'ɵɵviewQuerySignal', moduleName: CORE }; }
2633
2643
  static { this.contentQuerySignal = { name: 'ɵɵcontentQuerySignal', moduleName: CORE }; }
2634
2644
  static { this.queryAdvance = { name: 'ɵɵqueryAdvance', moduleName: CORE }; }
2645
+ // Two-way bindings
2646
+ static { this.twoWayProperty = { name: 'ɵɵtwoWayProperty', moduleName: CORE }; }
2647
+ static { this.twoWayBindingSet = { name: 'ɵɵtwoWayBindingSet', moduleName: CORE }; }
2648
+ static { this.twoWayListener = { name: 'ɵɵtwoWayListener', moduleName: CORE }; }
2635
2649
  static { this.NgOnChangesFeature = { name: 'ɵɵNgOnChangesFeature', moduleName: CORE }; }
2636
2650
  static { this.InheritDefinitionFeature = { name: 'ɵɵInheritDefinitionFeature', moduleName: CORE }; }
2637
2651
  static { this.CopyDefinitionFeature = { name: 'ɵɵCopyDefinitionFeature', moduleName: CORE }; }
@@ -2661,6 +2675,7 @@ class Identifiers {
2661
2675
  // type-checking
2662
2676
  static { this.InputSignalBrandWriteType = { name: 'ɵINPUT_SIGNAL_BRAND_WRITE_TYPE', moduleName: CORE }; }
2663
2677
  static { this.UnwrapDirectiveSignalInputs = { name: 'ɵUnwrapDirectiveSignalInputs', moduleName: CORE }; }
2678
+ static { this.unwrapWritableSignal = { name: 'ɵunwrapWritableSignal', moduleName: CORE }; }
2664
2679
  }
2665
2680
 
2666
2681
  const DASH_CASE_REGEXP = /-+([a-z0-9])/g;
@@ -4961,6 +4976,8 @@ const CHAINABLE_INSTRUCTIONS = new Set([
4961
4976
  Identifiers.textInterpolate8,
4962
4977
  Identifiers.textInterpolateV,
4963
4978
  Identifiers.templateCreate,
4979
+ Identifiers.twoWayProperty,
4980
+ Identifiers.twoWayListener,
4964
4981
  ]);
4965
4982
  /** Generates a call to a single instruction. */
4966
4983
  function invokeInstruction(span, reference, params) {
@@ -6933,8 +6950,6 @@ var ParsedPropertyType;
6933
6950
  ParsedPropertyType[ParsedPropertyType["TWO_WAY"] = 3] = "TWO_WAY";
6934
6951
  })(ParsedPropertyType || (ParsedPropertyType = {}));
6935
6952
  class ParsedEvent {
6936
- // Regular events have a target
6937
- // Animation events have a phase
6938
6953
  constructor(name, targetOrPhase, type, handler, sourceSpan, handlerSpan, keySpan) {
6939
6954
  this.name = name;
6940
6955
  this.targetOrPhase = targetOrPhase;
@@ -6978,32 +6993,10 @@ class EventHandlerVars {
6978
6993
  * used in an action binding (e.g. an event handler).
6979
6994
  */
6980
6995
  function convertActionBinding(localResolver, implicitReceiver, action, bindingId, baseSourceSpan, implicitReceiverAccesses, globals) {
6981
- if (!localResolver) {
6982
- localResolver = new DefaultLocalResolver(globals);
6983
- }
6984
- const actionWithoutBuiltins = convertPropertyBindingBuiltins({
6985
- createLiteralArrayConverter: (argCount) => {
6986
- // Note: no caching for literal arrays in actions.
6987
- return (args) => literalArr(args);
6988
- },
6989
- createLiteralMapConverter: (keys) => {
6990
- // Note: no caching for literal maps in actions.
6991
- return (values) => {
6992
- const entries = keys.map((k, i) => ({
6993
- key: k.key,
6994
- value: values[i],
6995
- quoted: k.quoted,
6996
- }));
6997
- return literalMap(entries);
6998
- };
6999
- },
7000
- createPipeConverter: (name) => {
7001
- throw new Error(`Illegal State: Actions are not allowed to contain pipes. Pipe: ${name}`);
7002
- }
7003
- }, action);
6996
+ localResolver ??= new DefaultLocalResolver(globals);
7004
6997
  const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, /* supportsInterpolation */ false, baseSourceSpan, implicitReceiverAccesses);
7005
6998
  const actionStmts = [];
7006
- flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
6999
+ flattenStatements(convertActionBuiltins(action).visit(visitor, _Mode.Statement), actionStmts);
7007
7000
  prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
7008
7001
  if (visitor.usesImplicitReceiver) {
7009
7002
  localResolver.notifyImplicitReceiverUse();
@@ -7018,6 +7011,77 @@ function convertActionBinding(localResolver, implicitReceiver, action, bindingId
7018
7011
  }
7019
7012
  return actionStmts;
7020
7013
  }
7014
+ function convertAssignmentActionBinding(localResolver, implicitReceiver, action, bindingId, baseSourceSpan, implicitReceiverAccesses, globals) {
7015
+ localResolver ??= new DefaultLocalResolver(globals);
7016
+ const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, /* supportsInterpolation */ false, baseSourceSpan, implicitReceiverAccesses);
7017
+ let convertedAction = convertActionBuiltins(action).visit(visitor, _Mode.Statement);
7018
+ // This should already have been asserted in the parser, but we verify it here just in case.
7019
+ if (!(convertedAction instanceof ExpressionStatement)) {
7020
+ throw new Error(`Illegal state: unsupported expression in two-way action binding.`);
7021
+ }
7022
+ // Converts `[(ngModel)]="name"` to `twoWayBindingSet(ctx.name, $event) || (ctx.name = $event)`.
7023
+ convertedAction = wrapAssignmentAction(convertedAction.expr).toStmt();
7024
+ const actionStmts = [];
7025
+ flattenStatements(convertedAction, actionStmts);
7026
+ prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
7027
+ // Assignment events always return `$event`.
7028
+ actionStmts.push(new ReturnStatement(EventHandlerVars.event));
7029
+ implicitReceiverAccesses?.add(EventHandlerVars.event.name);
7030
+ if (visitor.usesImplicitReceiver) {
7031
+ localResolver.notifyImplicitReceiverUse();
7032
+ }
7033
+ return actionStmts;
7034
+ }
7035
+ function wrapAssignmentReadExpression(ast) {
7036
+ return new ExternalExpr(Identifiers.twoWayBindingSet)
7037
+ .callFn([ast, EventHandlerVars.event])
7038
+ .or(ast.set(EventHandlerVars.event));
7039
+ }
7040
+ function isReadExpression$1(value) {
7041
+ return value instanceof ReadPropExpr || value instanceof ReadKeyExpr;
7042
+ }
7043
+ function wrapAssignmentAction(ast) {
7044
+ // The only officially supported expressions inside of a two-way binding are read expressions.
7045
+ if (isReadExpression$1(ast)) {
7046
+ return wrapAssignmentReadExpression(ast);
7047
+ }
7048
+ // However, historically the expression parser was handling two-way events by appending `=$event`
7049
+ // to the raw string before attempting to parse it. This has led to bugs over the years (see
7050
+ // #37809) and to unintentionally supporting unassignable events in the two-way binding. The
7051
+ // logic below aims to emulate the old behavior while still supporting the new output format
7052
+ // which uses `twoWayBindingSet`. Note that the generated code doesn't necessarily make sense
7053
+ // based on what the user wrote, for example the event binding for `[(value)]="a ? b : c"`
7054
+ // would produce `ctx.a ? ctx.b : ctx.c = $event`. We aim to reproduce what the parser used
7055
+ // to generate before #54154.
7056
+ if (ast instanceof BinaryOperatorExpr && isReadExpression$1(ast.rhs)) {
7057
+ // `a && b` -> `ctx.a && twoWayBindingSet(ctx.b, $event) || (ctx.b = $event)`
7058
+ return new BinaryOperatorExpr(ast.operator, ast.lhs, wrapAssignmentReadExpression(ast.rhs));
7059
+ }
7060
+ // Note: this also supports nullish coalescing expressions which
7061
+ // would've been downleveled to ternary expressions by this point.
7062
+ if (ast instanceof ConditionalExpr && isReadExpression$1(ast.falseCase)) {
7063
+ // `a ? b : c` -> `ctx.a ? ctx.b : twoWayBindingSet(ctx.c, $event) || (ctx.c = $event)`
7064
+ return new ConditionalExpr(ast.condition, ast.trueCase, wrapAssignmentReadExpression(ast.falseCase));
7065
+ }
7066
+ // `!!a` -> `twoWayBindingSet(ctx.a, $event) || (ctx.a = $event)`
7067
+ // Note: previously we'd actually produce `!!(ctx.a = $event)`, but the wrapping
7068
+ // node doesn't affect the result so we don't need to carry it over.
7069
+ if (ast instanceof NotExpr) {
7070
+ let expr = ast.condition;
7071
+ while (true) {
7072
+ if (expr instanceof NotExpr) {
7073
+ expr = expr.condition;
7074
+ }
7075
+ else {
7076
+ if (isReadExpression$1(expr)) {
7077
+ return wrapAssignmentReadExpression(expr);
7078
+ }
7079
+ break;
7080
+ }
7081
+ }
7082
+ }
7083
+ throw new Error(`Illegal state: unsupported expression in two-way action binding.`);
7084
+ }
7021
7085
  function convertPropertyBindingBuiltins(converterFactory, ast) {
7022
7086
  return convertBuiltins(converterFactory, ast);
7023
7087
  }
@@ -7101,6 +7165,29 @@ function convertBuiltins(converterFactory, ast) {
7101
7165
  const visitor = new _BuiltinAstConverter(converterFactory);
7102
7166
  return ast.visit(visitor);
7103
7167
  }
7168
+ function convertActionBuiltins(action) {
7169
+ const converterFactory = {
7170
+ createLiteralArrayConverter: () => {
7171
+ // Note: no caching for literal arrays in actions.
7172
+ return (args) => literalArr(args);
7173
+ },
7174
+ createLiteralMapConverter: (keys) => {
7175
+ // Note: no caching for literal maps in actions.
7176
+ return (values) => {
7177
+ const entries = keys.map((k, i) => ({
7178
+ key: k.key,
7179
+ value: values[i],
7180
+ quoted: k.quoted,
7181
+ }));
7182
+ return literalMap(entries);
7183
+ };
7184
+ },
7185
+ createPipeConverter: (name) => {
7186
+ throw new Error(`Illegal State: Actions are not allowed to contain pipes. Pipe: ${name}`);
7187
+ }
7188
+ };
7189
+ return convertPropertyBindingBuiltins(converterFactory, action);
7190
+ }
7104
7191
  function temporaryName(bindingId, temporaryNumber) {
7105
7192
  return `tmp_${bindingId}_${temporaryNumber}`;
7106
7193
  }
@@ -8984,46 +9071,54 @@ var OpKind;
8984
9071
  * An update up for a repeater.
8985
9072
  */
8986
9073
  OpKind[OpKind["Repeater"] = 35] = "Repeater";
9074
+ /**
9075
+ * An operation to bind an expression to the property side of a two-way binding.
9076
+ */
9077
+ OpKind[OpKind["TwoWayProperty"] = 36] = "TwoWayProperty";
9078
+ /**
9079
+ * An operation declaring the event side of a two-way binding.
9080
+ */
9081
+ OpKind[OpKind["TwoWayListener"] = 37] = "TwoWayListener";
8987
9082
  /**
8988
9083
  * The start of an i18n block.
8989
9084
  */
8990
- OpKind[OpKind["I18nStart"] = 36] = "I18nStart";
9085
+ OpKind[OpKind["I18nStart"] = 38] = "I18nStart";
8991
9086
  /**
8992
9087
  * A self-closing i18n on a single element.
8993
9088
  */
8994
- OpKind[OpKind["I18n"] = 37] = "I18n";
9089
+ OpKind[OpKind["I18n"] = 39] = "I18n";
8995
9090
  /**
8996
9091
  * The end of an i18n block.
8997
9092
  */
8998
- OpKind[OpKind["I18nEnd"] = 38] = "I18nEnd";
9093
+ OpKind[OpKind["I18nEnd"] = 40] = "I18nEnd";
8999
9094
  /**
9000
9095
  * An expression in an i18n message.
9001
9096
  */
9002
- OpKind[OpKind["I18nExpression"] = 39] = "I18nExpression";
9097
+ OpKind[OpKind["I18nExpression"] = 41] = "I18nExpression";
9003
9098
  /**
9004
9099
  * An instruction that applies a set of i18n expressions.
9005
9100
  */
9006
- OpKind[OpKind["I18nApply"] = 40] = "I18nApply";
9101
+ OpKind[OpKind["I18nApply"] = 42] = "I18nApply";
9007
9102
  /**
9008
9103
  * An instruction to create an ICU expression.
9009
9104
  */
9010
- OpKind[OpKind["IcuStart"] = 41] = "IcuStart";
9105
+ OpKind[OpKind["IcuStart"] = 43] = "IcuStart";
9011
9106
  /**
9012
9107
  * An instruction to update an ICU expression.
9013
9108
  */
9014
- OpKind[OpKind["IcuEnd"] = 42] = "IcuEnd";
9109
+ OpKind[OpKind["IcuEnd"] = 44] = "IcuEnd";
9015
9110
  /**
9016
9111
  * An instruction representing a placeholder in an ICU expression.
9017
9112
  */
9018
- OpKind[OpKind["IcuPlaceholder"] = 43] = "IcuPlaceholder";
9113
+ OpKind[OpKind["IcuPlaceholder"] = 45] = "IcuPlaceholder";
9019
9114
  /**
9020
9115
  * An i18n context containing information needed to generate an i18n message.
9021
9116
  */
9022
- OpKind[OpKind["I18nContext"] = 44] = "I18nContext";
9117
+ OpKind[OpKind["I18nContext"] = 46] = "I18nContext";
9023
9118
  /**
9024
9119
  * A creation op that corresponds to i18n attributes on an element.
9025
9120
  */
9026
- OpKind[OpKind["I18nAttributes"] = 45] = "I18nAttributes";
9121
+ OpKind[OpKind["I18nAttributes"] = 47] = "I18nAttributes";
9027
9122
  })(OpKind || (OpKind = {}));
9028
9123
  /**
9029
9124
  * Distinguishes different kinds of IR expressions.
@@ -9135,6 +9230,10 @@ var ExpressionKind;
9135
9230
  * An expression that will be automatically extracted to the component const array.
9136
9231
  */
9137
9232
  ExpressionKind[ExpressionKind["ConstCollected"] = 25] = "ConstCollected";
9233
+ /**
9234
+ * Operation that sets the value of a two-way binding.
9235
+ */
9236
+ ExpressionKind[ExpressionKind["TwoWayBindingSet"] = 26] = "TwoWayBindingSet";
9138
9237
  })(ExpressionKind || (ExpressionKind = {}));
9139
9238
  var VariableFlags;
9140
9239
  (function (VariableFlags) {
@@ -9220,6 +9319,10 @@ var BindingKind;
9220
9319
  * Animation property bindings.
9221
9320
  */
9222
9321
  BindingKind[BindingKind["Animation"] = 6] = "Animation";
9322
+ /**
9323
+ * Property side of a two-way binding.
9324
+ */
9325
+ BindingKind[BindingKind["TwoWayProperty"] = 7] = "TwoWayProperty";
9223
9326
  })(BindingKind || (BindingKind = {}));
9224
9327
  /**
9225
9328
  * Enumeration of possible times i18n params can be resolved.
@@ -9486,6 +9589,27 @@ function createPropertyOp(target, name, expression, isAnimationTrigger, security
9486
9589
  ...NEW_OP,
9487
9590
  };
9488
9591
  }
9592
+ /**
9593
+ * Create a `TwoWayPropertyOp`.
9594
+ */
9595
+ function createTwoWayPropertyOp(target, name, expression, securityContext, isStructuralTemplateAttribute, templateKind, i18nContext, i18nMessage, sourceSpan) {
9596
+ return {
9597
+ kind: OpKind.TwoWayProperty,
9598
+ target,
9599
+ name,
9600
+ expression,
9601
+ securityContext,
9602
+ sanitizer: null,
9603
+ isStructuralTemplateAttribute,
9604
+ templateKind,
9605
+ i18nContext,
9606
+ i18nMessage,
9607
+ sourceSpan,
9608
+ ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9609
+ ...TRAIT_CONSUMES_VARS,
9610
+ ...NEW_OP,
9611
+ };
9612
+ }
9489
9613
  /** Create a `StylePropOp`. */
9490
9614
  function createStylePropOp(xref, name, expression, unit, sourceSpan) {
9491
9615
  return {
@@ -9863,6 +9987,31 @@ class ResetViewExpr extends ExpressionBase {
9863
9987
  return new ResetViewExpr(this.expr.clone());
9864
9988
  }
9865
9989
  }
9990
+ class TwoWayBindingSetExpr extends ExpressionBase {
9991
+ constructor(target, value) {
9992
+ super();
9993
+ this.target = target;
9994
+ this.value = value;
9995
+ this.kind = ExpressionKind.TwoWayBindingSet;
9996
+ }
9997
+ visitExpression(visitor, context) {
9998
+ this.target.visitExpression(visitor, context);
9999
+ this.value.visitExpression(visitor, context);
10000
+ }
10001
+ isEquivalent(other) {
10002
+ return this.target.isEquivalent(other.target) && this.value.isEquivalent(other.value);
10003
+ }
10004
+ isConstant() {
10005
+ return false;
10006
+ }
10007
+ transformInternalExpressions(transform, flags) {
10008
+ this.target = transformExpressionsInExpression(this.target, transform, flags);
10009
+ this.value = transformExpressionsInExpression(this.value, transform, flags);
10010
+ }
10011
+ clone() {
10012
+ return new TwoWayBindingSetExpr(this.target, this.value);
10013
+ }
10014
+ }
9866
10015
  /**
9867
10016
  * Read of a variable declared as an `ir.VariableOp` and referenced through its `ir.XrefId`.
9868
10017
  */
@@ -10322,6 +10471,11 @@ function transformExpressionsInOp(op, transform, flags) {
10322
10471
  op.sanitizer =
10323
10472
  op.sanitizer && transformExpressionsInExpression(op.sanitizer, transform, flags);
10324
10473
  break;
10474
+ case OpKind.TwoWayProperty:
10475
+ op.expression = transformExpressionsInExpression(op.expression, transform, flags);
10476
+ op.sanitizer =
10477
+ op.sanitizer && transformExpressionsInExpression(op.sanitizer, transform, flags);
10478
+ break;
10325
10479
  case OpKind.I18nExpression:
10326
10480
  op.expression = transformExpressionsInExpression(op.expression, transform, flags);
10327
10481
  break;
@@ -10350,6 +10504,7 @@ function transformExpressionsInOp(op, transform, flags) {
10350
10504
  }
10351
10505
  break;
10352
10506
  case OpKind.Listener:
10507
+ case OpKind.TwoWayListener:
10353
10508
  for (const innerOp of op.handlerOps) {
10354
10509
  transformExpressionsInOp(innerOp, transform, flags | VisitorContextFlag.InChildOperation);
10355
10510
  }
@@ -10968,6 +11123,24 @@ function createListenerOp(target, targetSlot, name, tag, handlerOps, animationPh
10968
11123
  ...NEW_OP,
10969
11124
  };
10970
11125
  }
11126
+ /**
11127
+ * Create a `TwoWayListenerOp`.
11128
+ */
11129
+ function createTwoWayListenerOp(target, targetSlot, name, tag, handlerOps, sourceSpan) {
11130
+ const handlerList = new OpList();
11131
+ handlerList.push(handlerOps);
11132
+ return {
11133
+ kind: OpKind.TwoWayListener,
11134
+ target,
11135
+ targetSlot,
11136
+ tag,
11137
+ name,
11138
+ handlerOps: handlerList,
11139
+ handlerFnName: null,
11140
+ sourceSpan,
11141
+ ...NEW_OP,
11142
+ };
11143
+ }
10971
11144
  function createPipeOp(xref, slot, name) {
10972
11145
  return {
10973
11146
  kind: OpKind.Pipe,
@@ -11325,7 +11498,7 @@ class CompilationUnit {
11325
11498
  *ops() {
11326
11499
  for (const op of this.create) {
11327
11500
  yield op;
11328
- if (op.kind === OpKind.Listener) {
11501
+ if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
11329
11502
  for (const listenerOp of op.handlerOps) {
11330
11503
  yield listenerOp;
11331
11504
  }
@@ -11571,6 +11744,11 @@ function extractAttributes(job) {
11571
11744
  /* i18nMessage */ null, op.securityContext), lookupElement$2(elements, op.target));
11572
11745
  }
11573
11746
  break;
11747
+ case OpKind.TwoWayProperty:
11748
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.TwoWayProperty, null, op.name, /* expression */ null,
11749
+ /* i18nContext */ null,
11750
+ /* i18nMessage */ null, op.securityContext), lookupElement$2(elements, op.target));
11751
+ break;
11574
11752
  case OpKind.StyleProp:
11575
11753
  case OpKind.ClassProp:
11576
11754
  // TODO: Can style or class bindings be i18n attributes?
@@ -11604,6 +11782,15 @@ function extractAttributes(job) {
11604
11782
  }
11605
11783
  }
11606
11784
  break;
11785
+ case OpKind.TwoWayListener:
11786
+ // Two-way listeners aren't supported in host bindings.
11787
+ if (job.kind !== CompilationJobKind.Host) {
11788
+ const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, null, op.name, /* expression */ null,
11789
+ /* i18nContext */ null,
11790
+ /* i18nMessage */ null, SecurityContext.NONE);
11791
+ OpList.insertBefore(extractedAttributeOp, lookupElement$2(elements, op.target));
11792
+ }
11793
+ break;
11607
11794
  }
11608
11795
  }
11609
11796
  }
@@ -11692,6 +11879,15 @@ function specializeBindings(job) {
11692
11879
  OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind === BindingKind.Animation, op.securityContext, op.isStructuralTemplateAttribute, op.templateKind, op.i18nContext, op.i18nMessage, op.sourceSpan));
11693
11880
  }
11694
11881
  break;
11882
+ case BindingKind.TwoWayProperty:
11883
+ if (!(op.expression instanceof Expression)) {
11884
+ // We shouldn't be able to hit this code path since interpolations in two-way bindings
11885
+ // result in a parser error. We assert here so that downstream we can assume that
11886
+ // the value is always an expression.
11887
+ throw new Error(`Expected value of two-way property binding "${op.name}" to be an expression`);
11888
+ }
11889
+ OpList.replace(op, createTwoWayPropertyOp(op.target, op.name, op.expression, op.securityContext, op.isStructuralTemplateAttribute, op.templateKind, op.i18nContext, op.i18nMessage, op.sourceSpan));
11890
+ break;
11695
11891
  case BindingKind.I18n:
11696
11892
  case BindingKind.ClassName:
11697
11893
  case BindingKind.StyleProperty:
@@ -11728,6 +11924,8 @@ const CHAINABLE = new Set([
11728
11924
  Identifiers.syntheticHostListener,
11729
11925
  Identifiers.syntheticHostProperty,
11730
11926
  Identifiers.templateCreate,
11927
+ Identifiers.twoWayProperty,
11928
+ Identifiers.twoWayListener,
11731
11929
  ]);
11732
11930
  /**
11733
11931
  * Post-process a reified view compilation and convert sequential calls to chainable instructions
@@ -12001,7 +12199,7 @@ class ElementAttributes {
12001
12199
  return this.byKind.get(BindingKind.StyleProperty) ?? FLYWEIGHT_ARRAY;
12002
12200
  }
12003
12201
  get bindings() {
12004
- return this.byKind.get(BindingKind.Property) ?? FLYWEIGHT_ARRAY;
12202
+ return this.propertyBindings ?? FLYWEIGHT_ARRAY;
12005
12203
  }
12006
12204
  get template() {
12007
12205
  return this.byKind.get(BindingKind.Template) ?? FLYWEIGHT_ARRAY;
@@ -12013,6 +12211,7 @@ class ElementAttributes {
12013
12211
  this.compatibility = compatibility;
12014
12212
  this.known = new Map();
12015
12213
  this.byKind = new Map;
12214
+ this.propertyBindings = null;
12016
12215
  this.projectAs = null;
12017
12216
  }
12018
12217
  isKnown(kind, name, value) {
@@ -12062,10 +12261,16 @@ class ElementAttributes {
12062
12261
  }
12063
12262
  }
12064
12263
  arrayFor(kind) {
12065
- if (!this.byKind.has(kind)) {
12066
- this.byKind.set(kind, []);
12264
+ if (kind === BindingKind.Property || kind === BindingKind.TwoWayProperty) {
12265
+ this.propertyBindings ??= [];
12266
+ return this.propertyBindings;
12267
+ }
12268
+ else {
12269
+ if (!this.byKind.has(kind)) {
12270
+ this.byKind.set(kind, []);
12271
+ }
12272
+ return this.byKind.get(kind);
12067
12273
  }
12068
- return this.byKind.get(kind);
12069
12274
  }
12070
12275
  }
12071
12276
  /**
@@ -12168,7 +12373,9 @@ function createDeferDepsFns(job) {
12168
12373
  for (const dep of op.metadata.deps) {
12169
12374
  if (dep.isDeferrable) {
12170
12375
  // Callback function, e.g. `m () => m.MyCmp;`.
12171
- const innerFn = arrowFn([new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(dep.symbolName));
12376
+ const innerFn = arrowFn(
12377
+ // Default imports are always accessed through the `default` property.
12378
+ [new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(dep.isDefaultImport ? 'default' : dep.symbolName));
12172
12379
  // Dynamic import, e.g. `import('./a').then(...)`.
12173
12380
  const importExpr = (new DynamicImportExpr(dep.importPath)).prop('then').callFn([innerFn]);
12174
12381
  dependencies.push(importExpr);
@@ -12995,6 +13202,7 @@ function recursivelyProcessView(view, parentScope) {
12995
13202
  }
12996
13203
  break;
12997
13204
  case OpKind.Listener:
13205
+ case OpKind.TwoWayListener:
12998
13206
  // Prepend variables to listener handler functions.
12999
13207
  op.handlerOps.prepend(generateVariablesInScopeForView(view, scope));
13000
13208
  break;
@@ -13658,15 +13866,12 @@ class Parser$1 {
13658
13866
  this._lexer = _lexer;
13659
13867
  this.errors = [];
13660
13868
  }
13661
- parseAction(input, isAssignmentEvent, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
13869
+ parseAction(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
13662
13870
  this._checkNoInterpolation(input, location, interpolationConfig);
13663
13871
  const sourceToLex = this._stripComments(input);
13664
13872
  const tokens = this._lexer.tokenize(sourceToLex);
13665
- let flags = 1 /* ParseFlags.Action */;
13666
- if (isAssignmentEvent) {
13667
- flags |= 2 /* ParseFlags.AssignmentEvent */;
13668
- }
13669
- const ast = new _ParseAST(input, location, absoluteOffset, tokens, flags, this.errors, 0).parseChain();
13873
+ const ast = new _ParseAST(input, location, absoluteOffset, tokens, 1 /* ParseFlags.Action */, this.errors, 0)
13874
+ .parseChain();
13670
13875
  return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
13671
13876
  }
13672
13877
  parseBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
@@ -14143,7 +14348,7 @@ class _ParseAST {
14143
14348
  let result = this.parseExpression();
14144
14349
  if (this.consumeOptionalOperator('|')) {
14145
14350
  if (this.parseFlags & 1 /* ParseFlags.Action */) {
14146
- this.error('Cannot have a pipe in an action expression');
14351
+ this.error(`Cannot have a pipe in an action expression`);
14147
14352
  }
14148
14353
  do {
14149
14354
  const nameStart = this.inputIndex;
@@ -14485,7 +14690,7 @@ class _ParseAST {
14485
14690
  const nameSpan = this.sourceSpan(nameStart);
14486
14691
  let receiver;
14487
14692
  if (isSafe) {
14488
- if (this.consumeOptionalAssignment()) {
14693
+ if (this.consumeOptionalOperator('=')) {
14489
14694
  this.error('The \'?.\' operator cannot be used in the assignment');
14490
14695
  receiver = new EmptyExpr$1(this.span(start), this.sourceSpan(start));
14491
14696
  }
@@ -14494,7 +14699,7 @@ class _ParseAST {
14494
14699
  }
14495
14700
  }
14496
14701
  else {
14497
- if (this.consumeOptionalAssignment()) {
14702
+ if (this.consumeOptionalOperator('=')) {
14498
14703
  if (!(this.parseFlags & 1 /* ParseFlags.Action */)) {
14499
14704
  this.error('Bindings cannot contain assignments');
14500
14705
  return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
@@ -14521,22 +14726,6 @@ class _ParseAST {
14521
14726
  return isSafe ? new SafeCall(span, sourceSpan, receiver, args, argumentSpan) :
14522
14727
  new Call(span, sourceSpan, receiver, args, argumentSpan);
14523
14728
  }
14524
- consumeOptionalAssignment() {
14525
- // When parsing assignment events (originating from two-way-binding aka banana-in-a-box syntax),
14526
- // it is valid for the primary expression to be terminated by the non-null operator. This
14527
- // primary expression is substituted as LHS of the assignment operator to achieve
14528
- // two-way-binding, such that the LHS could be the non-null operator. The grammar doesn't
14529
- // naturally allow for this syntax, so assignment events are parsed specially.
14530
- if ((this.parseFlags & 2 /* ParseFlags.AssignmentEvent */) && this.next.isOperator('!') &&
14531
- this.peek(1).isOperator('=')) {
14532
- // First skip over the ! operator.
14533
- this.advance();
14534
- // Then skip over the = operator, to fully consume the optional assignment operator.
14535
- this.advance();
14536
- return true;
14537
- }
14538
- return this.consumeOptionalOperator('=');
14539
- }
14540
14729
  parseCallArguments() {
14541
14730
  if (this.next.isCharacter($RPAREN))
14542
14731
  return [];
@@ -20614,7 +20803,10 @@ function nameFunctionsAndVariables(job) {
20614
20803
  }
20615
20804
  function addNamesToView(unit, baseName, state, compatibility) {
20616
20805
  if (unit.fnName === null) {
20617
- unit.fnName = sanitizeIdentifier(`${baseName}_${unit.job.fnSuffix}`);
20806
+ // Ensure unique names for view units. This is necessary because there might be multiple
20807
+ // components with same names in the context of the same pool. Only add the suffix
20808
+ // if really needed.
20809
+ unit.fnName = unit.job.pool.uniqueName(sanitizeIdentifier(`${baseName}_${unit.job.fnSuffix}`), /* alwaysIncludeSuffix */ false);
20618
20810
  }
20619
20811
  // Keep track of the names we assign to variables in the view. We'll need to propagate these
20620
20812
  // into reads of those variables afterwards.
@@ -20647,6 +20839,15 @@ function addNamesToView(unit, baseName, state, compatibility) {
20647
20839
  }
20648
20840
  op.handlerFnName = sanitizeIdentifier(op.handlerFnName);
20649
20841
  break;
20842
+ case OpKind.TwoWayListener:
20843
+ if (op.handlerFnName !== null) {
20844
+ break;
20845
+ }
20846
+ if (op.targetSlot.slot === null) {
20847
+ throw new Error(`Expected a slot to be assigned`);
20848
+ }
20849
+ op.handlerFnName = sanitizeIdentifier(`${unit.fnName}_${op.tag.replace('-', '_')}_${op.name}_${op.targetSlot.slot}_listener`);
20850
+ break;
20650
20851
  case OpKind.Variable:
20651
20852
  varNames.set(op.xref, getVariableName(unit, op.variable, state));
20652
20853
  break;
@@ -20761,7 +20962,7 @@ function stripImportant(name) {
20761
20962
  function mergeNextContextExpressions(job) {
20762
20963
  for (const unit of job.units) {
20763
20964
  for (const op of unit.create) {
20764
- if (op.kind === OpKind.Listener) {
20965
+ if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
20765
20966
  mergeNextContextsInOps(op.handlerOps);
20766
20967
  }
20767
20968
  }
@@ -20902,6 +21103,14 @@ function kindWithInterpolationTest(kind, interpolation) {
20902
21103
  return op.kind === kind && interpolation === op.expression instanceof Interpolation;
20903
21104
  };
20904
21105
  }
21106
+ function basicListenerKindTest(op) {
21107
+ return (op.kind === OpKind.Listener && !(op.hostListener && op.isAnimationListener)) ||
21108
+ op.kind === OpKind.TwoWayListener;
21109
+ }
21110
+ function nonInterpolationPropertyKindTest(op) {
21111
+ return (op.kind === OpKind.Property || op.kind === OpKind.TwoWayProperty) &&
21112
+ !(op.expression instanceof Interpolation);
21113
+ }
20905
21114
  /**
20906
21115
  * Defines the groups based on `OpKind` that ops will be divided into, for the various create
20907
21116
  * op kinds. Ops will be collected into groups, then optionally transformed, before recombining
@@ -20909,7 +21118,7 @@ function kindWithInterpolationTest(kind, interpolation) {
20909
21118
  */
20910
21119
  const CREATE_ORDERING = [
20911
21120
  { test: op => op.kind === OpKind.Listener && op.hostListener && op.isAnimationListener },
20912
- { test: op => op.kind === OpKind.Listener && !(op.hostListener && op.isAnimationListener) },
21121
+ { test: basicListenerKindTest },
20913
21122
  ];
20914
21123
  /**
20915
21124
  * Defines the groups based on `OpKind` that ops will be divided into, for the various update
@@ -20922,7 +21131,7 @@ const UPDATE_ORDERING = [
20922
21131
  { test: kindTest(OpKind.ClassProp) },
20923
21132
  { test: kindWithInterpolationTest(OpKind.Attribute, true) },
20924
21133
  { test: kindWithInterpolationTest(OpKind.Property, true) },
20925
- { test: kindWithInterpolationTest(OpKind.Property, false) },
21134
+ { test: nonInterpolationPropertyKindTest },
20926
21135
  { test: kindWithInterpolationTest(OpKind.Attribute, false) },
20927
21136
  ];
20928
21137
  /**
@@ -20941,8 +21150,9 @@ const UPDATE_HOST_ORDERING = [
20941
21150
  * The set of all op kinds we handle in the reordering phase.
20942
21151
  */
20943
21152
  const handledOpKinds = new Set([
20944
- OpKind.Listener, OpKind.StyleMap, OpKind.ClassMap, OpKind.StyleProp,
20945
- OpKind.ClassProp, OpKind.Property, OpKind.HostProperty, OpKind.Attribute
21153
+ OpKind.Listener, OpKind.TwoWayListener, OpKind.StyleMap, OpKind.ClassMap,
21154
+ OpKind.StyleProp, OpKind.ClassProp, OpKind.Property, OpKind.TwoWayProperty,
21155
+ OpKind.HostProperty, OpKind.Attribute
20946
21156
  ]);
20947
21157
  /**
20948
21158
  * Many type of operations have ordering constraints that must be respected. For example, a
@@ -21408,6 +21618,12 @@ function listener(name, handlerFn, eventTargetResolver, syntheticHost, sourceSpa
21408
21618
  }
21409
21619
  return call(syntheticHost ? Identifiers.syntheticHostListener : Identifiers.listener, args, sourceSpan);
21410
21620
  }
21621
+ function twoWayBindingSet(target, value) {
21622
+ return importExpr(Identifiers.twoWayBindingSet).callFn([target, value]);
21623
+ }
21624
+ function twoWayListener(name, handlerFn, sourceSpan) {
21625
+ return call(Identifiers.twoWayListener, [literal(name), handlerFn], sourceSpan);
21626
+ }
21411
21627
  function pipe(slot, name) {
21412
21628
  return call(Identifiers.pipe, [
21413
21629
  literal(slot),
@@ -21568,6 +21784,13 @@ function property(name, expression, sanitizer, sourceSpan) {
21568
21784
  }
21569
21785
  return call(Identifiers.property, args, sourceSpan);
21570
21786
  }
21787
+ function twoWayProperty(name, expression, sanitizer, sourceSpan) {
21788
+ const args = [literal(name), expression];
21789
+ if (sanitizer !== null) {
21790
+ args.push(sanitizer);
21791
+ }
21792
+ return call(Identifiers.twoWayProperty, args, sourceSpan);
21793
+ }
21571
21794
  function attribute(name, expression, sanitizer, namespace) {
21572
21795
  const args = [literal(name), expression];
21573
21796
  if (sanitizer !== null || namespace !== null) {
@@ -22010,6 +22233,9 @@ function reifyCreateOperations(unit, ops) {
22010
22233
  }
22011
22234
  OpList.replace(op, listener(op.name, listenerFn, eventTargetResolver, op.hostListener && op.isAnimationListener, op.sourceSpan));
22012
22235
  break;
22236
+ case OpKind.TwoWayListener:
22237
+ OpList.replace(op, twoWayListener(op.name, reifyListenerHandler(unit, op.handlerFnName, op.handlerOps, true), op.sourceSpan));
22238
+ break;
22013
22239
  case OpKind.Variable:
22014
22240
  if (op.variable.name === null) {
22015
22241
  throw new Error(`AssertionError: unnamed variable ${op.xref}`);
@@ -22118,6 +22344,9 @@ function reifyUpdateOperations(_unit, ops) {
22118
22344
  OpList.replace(op, property(op.name, op.expression, op.sanitizer, op.sourceSpan));
22119
22345
  }
22120
22346
  break;
22347
+ case OpKind.TwoWayProperty:
22348
+ OpList.replace(op, twoWayProperty(op.name, op.expression, op.sanitizer, op.sourceSpan));
22349
+ break;
22121
22350
  case OpKind.StyleProp:
22122
22351
  if (op.expression instanceof Interpolation) {
22123
22352
  OpList.replace(op, stylePropInterpolate(op.name, op.expression.strings, op.expression.expressions, op.unit, op.sourceSpan));
@@ -22215,6 +22444,8 @@ function reifyIrExpression(expr) {
22215
22444
  return reference(expr.targetSlot.slot + 1 + expr.offset);
22216
22445
  case ExpressionKind.LexicalRead:
22217
22446
  throw new Error(`AssertionError: unresolved LexicalRead of ${expr.name}`);
22447
+ case ExpressionKind.TwoWayBindingSet:
22448
+ throw new Error(`AssertionError: unresolved TwoWayBindingSet`);
22218
22449
  case ExpressionKind.RestoreView:
22219
22450
  if (typeof expr.view === 'number') {
22220
22451
  throw new Error(`AssertionError: unresolved RestoreView`);
@@ -22375,6 +22606,7 @@ function processLexicalScope$1(view, ops) {
22375
22606
  }
22376
22607
  break;
22377
22608
  case OpKind.Listener:
22609
+ case OpKind.TwoWayListener:
22378
22610
  processLexicalScope$1(view, op.handlerOps);
22379
22611
  break;
22380
22612
  }
@@ -22410,10 +22642,13 @@ function resolveDollarEvent(job) {
22410
22642
  }
22411
22643
  function transformDollarEvent(unit, ops) {
22412
22644
  for (const op of ops) {
22413
- if (op.kind === OpKind.Listener) {
22645
+ if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
22414
22646
  transformExpressionsInOp(op, (expr) => {
22415
22647
  if (expr instanceof LexicalReadExpr && expr.name === '$event') {
22416
- op.consumesDollarEvent = true;
22648
+ // Two-way listeners always consume `$event` so they omit this field.
22649
+ if (op.kind === OpKind.Listener) {
22650
+ op.consumesDollarEvent = true;
22651
+ }
22417
22652
  return new ReadVarExpr(expr.name);
22418
22653
  }
22419
22654
  return expr;
@@ -22788,6 +23023,7 @@ function processLexicalScope(unit, ops, savedView) {
22788
23023
  }
22789
23024
  break;
22790
23025
  case OpKind.Listener:
23026
+ case OpKind.TwoWayListener:
22791
23027
  // Listener functions have separate variable declarations, so process them as a separate
22792
23028
  // lexical scope.
22793
23029
  processLexicalScope(unit, op.handlerOps, savedView);
@@ -22798,7 +23034,7 @@ function processLexicalScope(unit, ops, savedView) {
22798
23034
  // scope. Also, look for `ir.RestoreViewExpr`s and match them with the snapshotted view context
22799
23035
  // variable.
22800
23036
  for (const op of ops) {
22801
- if (op.kind == OpKind.Listener) {
23037
+ if (op.kind == OpKind.Listener || op.kind === OpKind.TwoWayListener) {
22802
23038
  // Listeners were already processed above with their own scopes.
22803
23039
  continue;
22804
23040
  }
@@ -22947,6 +23183,75 @@ function getOnlySecurityContext(securityContext) {
22947
23183
  return securityContext;
22948
23184
  }
22949
23185
 
23186
+ /**
23187
+ * Transforms a `TwoWayBindingSet` expression into an expression that either
23188
+ * sets a value through the `twoWayBindingSet` instruction or falls back to setting
23189
+ * the value directly. E.g. the expression `TwoWayBindingSet(target, value)` becomes:
23190
+ * `ng.twoWayBindingSet(target, value) || (target = value)`.
23191
+ */
23192
+ function transformTwoWayBindingSet(job) {
23193
+ for (const unit of job.units) {
23194
+ for (const op of unit.create) {
23195
+ if (op.kind === OpKind.TwoWayListener) {
23196
+ transformExpressionsInOp(op, (expr) => {
23197
+ if (expr instanceof TwoWayBindingSetExpr) {
23198
+ return wrapAction(expr.target, expr.value);
23199
+ }
23200
+ return expr;
23201
+ }, VisitorContextFlag.InChildOperation);
23202
+ }
23203
+ }
23204
+ }
23205
+ }
23206
+ function wrapSetOperation(target, value) {
23207
+ return twoWayBindingSet(target, value).or(target.set(value));
23208
+ }
23209
+ function isReadExpression(value) {
23210
+ return value instanceof ReadPropExpr || value instanceof ReadKeyExpr;
23211
+ }
23212
+ function wrapAction(target, value) {
23213
+ // The only officially supported expressions inside of a two-way binding are read expressions.
23214
+ if (isReadExpression(target)) {
23215
+ return wrapSetOperation(target, value);
23216
+ }
23217
+ // However, historically the expression parser was handling two-way events by appending `=$event`
23218
+ // to the raw string before attempting to parse it. This has led to bugs over the years (see
23219
+ // #37809) and to unintentionally supporting unassignable events in the two-way binding. The
23220
+ // logic below aims to emulate the old behavior while still supporting the new output format
23221
+ // which uses `twoWayBindingSet`. Note that the generated code doesn't necessarily make sense
23222
+ // based on what the user wrote, for example the event binding for `[(value)]="a ? b : c"`
23223
+ // would produce `ctx.a ? ctx.b : ctx.c = $event`. We aim to reproduce what the parser used
23224
+ // to generate before #54154.
23225
+ if (target instanceof BinaryOperatorExpr && isReadExpression(target.rhs)) {
23226
+ // `a && b` -> `ctx.a && twoWayBindingSet(ctx.b, $event) || (ctx.b = $event)`
23227
+ return new BinaryOperatorExpr(target.operator, target.lhs, wrapSetOperation(target.rhs, value));
23228
+ }
23229
+ // Note: this also supports nullish coalescing expressions which
23230
+ // would've been downleveled to ternary expressions by this point.
23231
+ if (target instanceof ConditionalExpr && isReadExpression(target.falseCase)) {
23232
+ // `a ? b : c` -> `ctx.a ? ctx.b : twoWayBindingSet(ctx.c, $event) || (ctx.c = $event)`
23233
+ return new ConditionalExpr(target.condition, target.trueCase, wrapSetOperation(target.falseCase, value));
23234
+ }
23235
+ // `!!a` -> `twoWayBindingSet(ctx.a, $event) || (ctx.a = $event)`
23236
+ // Note: previously we'd actually produce `!!(ctx.a = $event)`, but the wrapping
23237
+ // node doesn't affect the result so we don't need to carry it over.
23238
+ if (target instanceof NotExpr) {
23239
+ let expr = target.condition;
23240
+ while (true) {
23241
+ if (expr instanceof NotExpr) {
23242
+ expr = expr.condition;
23243
+ }
23244
+ else {
23245
+ if (isReadExpression(expr)) {
23246
+ return wrapSetOperation(expr, value);
23247
+ }
23248
+ break;
23249
+ }
23250
+ }
23251
+ }
23252
+ throw new Error(`Unsupported expression in two-way action binding.`);
23253
+ }
23254
+
22950
23255
  /**
22951
23256
  * When inside of a listener, we may need access to one or more enclosing views. Therefore, each
22952
23257
  * view should save the current view, and each listener must have the ability to restore the
@@ -22962,7 +23267,7 @@ function saveAndRestoreView(job) {
22962
23267
  }, new GetCurrentViewExpr(), VariableFlags.None),
22963
23268
  ]);
22964
23269
  for (const op of unit.create) {
22965
- if (op.kind !== OpKind.Listener) {
23270
+ if (op.kind !== OpKind.Listener && op.kind !== OpKind.TwoWayListener) {
22966
23271
  continue;
22967
23272
  }
22968
23273
  // Embedded views always need the save/restore view operation.
@@ -23152,7 +23457,7 @@ function generateTemporaries(ops) {
23152
23457
  generatedStatements.push(...Array.from(new Set(defs.values()))
23153
23458
  .map(name => createStatementOp(new DeclareVarStmt(name))));
23154
23459
  opCount++;
23155
- if (op.kind === OpKind.Listener) {
23460
+ if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
23156
23461
  op.handlerOps.prepend(generateTemporaries(op.handlerOps));
23157
23462
  }
23158
23463
  }
@@ -23400,6 +23705,9 @@ function varsUsedByOp(op) {
23400
23705
  slots += op.expression.expressions.length;
23401
23706
  }
23402
23707
  return slots;
23708
+ case OpKind.TwoWayProperty:
23709
+ // Two-way properties can only have expressions so they only need one variable slot.
23710
+ return 1;
23403
23711
  case OpKind.StyleProp:
23404
23712
  case OpKind.ClassProp:
23405
23713
  case OpKind.StyleMap:
@@ -23473,14 +23781,14 @@ function optimizeVariables(job) {
23473
23781
  inlineAlwaysInlineVariables(unit.create);
23474
23782
  inlineAlwaysInlineVariables(unit.update);
23475
23783
  for (const op of unit.create) {
23476
- if (op.kind === OpKind.Listener) {
23784
+ if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
23477
23785
  inlineAlwaysInlineVariables(op.handlerOps);
23478
23786
  }
23479
23787
  }
23480
23788
  optimizeVariablesInOpList(unit.create, job.compatibility);
23481
23789
  optimizeVariablesInOpList(unit.update, job.compatibility);
23482
23790
  for (const op of unit.create) {
23483
- if (op.kind === OpKind.Listener) {
23791
+ if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
23484
23792
  optimizeVariablesInOpList(op.handlerOps, job.compatibility);
23485
23793
  }
23486
23794
  }
@@ -23935,6 +24243,7 @@ const phases = [
23935
24243
  { kind: CompilationJobKind.Tmpl, fn: generateTrackVariables },
23936
24244
  { kind: CompilationJobKind.Both, fn: resolveNames },
23937
24245
  { kind: CompilationJobKind.Tmpl, fn: resolveDeferTargetNames },
24246
+ { kind: CompilationJobKind.Tmpl, fn: transformTwoWayBindingSet },
23938
24247
  { kind: CompilationJobKind.Tmpl, fn: optimizeTrackFns },
23939
24248
  { kind: CompilationJobKind.Both, fn: resolveContexts },
23940
24249
  { kind: CompilationJobKind.Both, fn: resolveSanitizers },
@@ -24731,8 +25040,7 @@ function convertAstWithInterpolation(job, value, i18nMeta, sourceSpan) {
24731
25040
  // TODO: Can we populate Template binding kinds in ingest?
24732
25041
  const BINDING_KINDS = new Map([
24733
25042
  [0 /* e.BindingType.Property */, BindingKind.Property],
24734
- // TODO(crisbeto): we'll need a different BindingKind for two-way bindings.
24735
- [5 /* e.BindingType.TwoWay */, BindingKind.Property],
25043
+ [5 /* e.BindingType.TwoWay */, BindingKind.TwoWayProperty],
24736
25044
  [1 /* e.BindingType.Attribute */, BindingKind.Attribute],
24737
25045
  [2 /* e.BindingType.Class */, BindingKind.ClassName],
24738
25046
  [3 /* e.BindingType.Style */, BindingKind.StyleProperty],
@@ -24776,12 +25084,20 @@ function asMessage(i18nMeta) {
24776
25084
  */
24777
25085
  function ingestElementBindings(unit, op, element) {
24778
25086
  let bindings = new Array();
25087
+ let i18nAttributeBindingNames = new Set();
24779
25088
  for (const attr of element.attributes) {
24780
25089
  // Attribute literal bindings, such as `attr.foo="bar"`.
24781
25090
  const securityContext = domSchema.securityContext(element.name, attr.name, true);
24782
25091
  bindings.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, convertAstWithInterpolation(unit.job, attr.value, attr.i18n), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
25092
+ if (attr.i18n) {
25093
+ i18nAttributeBindingNames.add(attr.name);
25094
+ }
24783
25095
  }
24784
25096
  for (const input of element.inputs) {
25097
+ if (i18nAttributeBindingNames.has(input.name)) {
25098
+ console.error(`On component ${unit.job.componentName}, the binding ${input
25099
+ .name} is both an i18n attribute and a property. You may want to remove the property binding. This will become a compilation error in future versions of Angular.`);
25100
+ }
24785
25101
  // All dynamic bindings (both attribute and property bindings).
24786
25102
  bindings.push(createBindingOp(op.xref, BINDING_KINDS.get(input.type), input.name, convertAstWithInterpolation(unit.job, astOf(input.value), input.i18n), input.unit, input.securityContext, false, false, null, asMessage(input.i18n) ?? null, input.sourceSpan));
24787
25103
  }
@@ -24791,7 +25107,12 @@ function ingestElementBindings(unit, op, element) {
24791
25107
  if (output.type === 1 /* e.ParsedEventType.Animation */ && output.phase === null) {
24792
25108
  throw Error('Animation listener should have a phase');
24793
25109
  }
24794
- unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
25110
+ if (output.type === 2 /* e.ParsedEventType.TwoWay */) {
25111
+ unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
25112
+ }
25113
+ else {
25114
+ unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
25115
+ }
24795
25116
  }
24796
25117
  // If any of the bindings on this element have an i18n message, then an i18n attrs configuration
24797
25118
  // op is also required.
@@ -24830,7 +25151,12 @@ function ingestTemplateBindings(unit, op, template, templateKind) {
24830
25151
  throw Error('Animation listener should have a phase');
24831
25152
  }
24832
25153
  if (templateKind === TemplateKind.NgTemplate) {
24833
- unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
25154
+ if (output.type === 2 /* e.ParsedEventType.TwoWay */) {
25155
+ unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
25156
+ }
25157
+ else {
25158
+ unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
25159
+ }
24834
25160
  }
24835
25161
  if (templateKind === TemplateKind.Structural &&
24836
25162
  output.type !== 1 /* e.ParsedEventType.Animation */) {
@@ -24877,14 +25203,19 @@ function createTemplateBinding(view, xref, type, name, value, unit, securityCont
24877
25203
  // If this is a structural template, then several kinds of bindings should not result in an
24878
25204
  // update instruction.
24879
25205
  if (templateKind === TemplateKind.Structural) {
24880
- if (!isStructuralTemplateAttribute &&
24881
- (type === 0 /* e.BindingType.Property */ || type === 5 /* e.BindingType.TwoWay */ ||
24882
- type === 2 /* e.BindingType.Class */ || type === 3 /* e.BindingType.Style */)) {
24883
- // Because this binding doesn't really target the ng-template, it must be a binding on an
24884
- // inner node of a structural template. We can't skip it entirely, because we still need it on
24885
- // the ng-template's consts (e.g. for the purposes of directive matching). However, we should
24886
- // not generate an update instruction for it.
24887
- return createExtractedAttributeOp(xref, BindingKind.Property, null, name, null, null, i18nMessage, securityContext);
25206
+ if (!isStructuralTemplateAttribute) {
25207
+ switch (type) {
25208
+ case 0 /* e.BindingType.Property */:
25209
+ case 2 /* e.BindingType.Class */:
25210
+ case 3 /* e.BindingType.Style */:
25211
+ // Because this binding doesn't really target the ng-template, it must be a binding on an
25212
+ // inner node of a structural template. We can't skip it entirely, because we still need
25213
+ // it on the ng-template's consts (e.g. for the purposes of directive matching). However,
25214
+ // we should not generate an update instruction for it.
25215
+ return createExtractedAttributeOp(xref, BindingKind.Property, null, name, null, null, i18nMessage, securityContext);
25216
+ case 5 /* e.BindingType.TwoWay */:
25217
+ return createExtractedAttributeOp(xref, BindingKind.TwoWayProperty, null, name, null, null, i18nMessage, securityContext);
25218
+ }
24888
25219
  }
24889
25220
  if (!isTextBinding && (type === 1 /* e.BindingType.Attribute */ || type === 4 /* e.BindingType.Animation */)) {
24890
25221
  // Again, this binding doesn't really target the ng-template; it actually targets the element
@@ -24932,6 +25263,25 @@ function makeListenerHandlerOps(unit, handler, handlerSpan) {
24932
25263
  handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
24933
25264
  return handlerOps;
24934
25265
  }
25266
+ function makeTwoWayListenerHandlerOps(unit, handler, handlerSpan) {
25267
+ handler = astOf(handler);
25268
+ const handlerOps = new Array();
25269
+ if (handler instanceof Chain) {
25270
+ if (handler.expressions.length === 1) {
25271
+ handler = handler.expressions[0];
25272
+ }
25273
+ else {
25274
+ // This is validated during parsing already, but we do it here just in case.
25275
+ throw new Error('Expected two-way listener to have a single expression.');
25276
+ }
25277
+ }
25278
+ const handlerExpr = convertAst(handler, unit.job, handlerSpan);
25279
+ const eventReference = new LexicalReadExpr('$event');
25280
+ const twoWaySetExpr = new TwoWayBindingSetExpr(handlerExpr, eventReference);
25281
+ handlerOps.push(createStatementOp(new ExpressionStatement(twoWaySetExpr)));
25282
+ handlerOps.push(createStatementOp(new ReturnStatement(eventReference)));
25283
+ return handlerOps;
25284
+ }
24935
25285
  function astOf(ast) {
24936
25286
  return ast instanceof ASTWithSource ? ast.ast : ast;
24937
25287
  }
@@ -25434,7 +25784,7 @@ class BindingParser {
25434
25784
  if (keySpan !== undefined) {
25435
25785
  keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
25436
25786
  }
25437
- this._parseAnimationEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetEvents, keySpan);
25787
+ this._parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan);
25438
25788
  }
25439
25789
  else {
25440
25790
  this._parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan);
@@ -25444,11 +25794,11 @@ class BindingParser {
25444
25794
  const prop = this._schemaRegistry.getMappedPropName(propName);
25445
25795
  return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
25446
25796
  }
25447
- _parseAnimationEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetEvents, keySpan) {
25797
+ _parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan) {
25448
25798
  const matches = splitAtPeriod(name, [name, '']);
25449
25799
  const eventName = matches[0];
25450
25800
  const phase = matches[1].toLowerCase();
25451
- const ast = this._parseAction(expression, isAssignmentEvent, handlerSpan);
25801
+ const ast = this._parseAction(expression, handlerSpan);
25452
25802
  targetEvents.push(new ParsedEvent(eventName, phase, 1 /* ParsedEventType.Animation */, ast, sourceSpan, handlerSpan, keySpan));
25453
25803
  if (eventName.length === 0) {
25454
25804
  this._reportError(`Animation event name is missing in binding`, sourceSpan);
@@ -25465,17 +25815,24 @@ class BindingParser {
25465
25815
  _parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
25466
25816
  // long format: 'target: eventName'
25467
25817
  const [target, eventName] = splitAtColon(name, [null, name]);
25468
- const ast = this._parseAction(expression, isAssignmentEvent, handlerSpan);
25818
+ const prevErrorCount = this.errors.length;
25819
+ const ast = this._parseAction(expression, handlerSpan);
25820
+ const isValid = this.errors.length === prevErrorCount;
25469
25821
  targetMatchableAttrs.push([name, ast.source]);
25822
+ // Don't try to validate assignment events if there were other
25823
+ // parsing errors to avoid adding more noise to the error logs.
25824
+ if (isAssignmentEvent && isValid && !this._isAllowedAssignmentEvent(ast)) {
25825
+ this._reportError('Unsupported expression in a two-way binding', sourceSpan);
25826
+ }
25470
25827
  targetEvents.push(new ParsedEvent(eventName, target, isAssignmentEvent ? 2 /* ParsedEventType.TwoWay */ : 0 /* ParsedEventType.Regular */, ast, sourceSpan, handlerSpan, keySpan));
25471
25828
  // Don't detect directives for event names for now,
25472
25829
  // so don't add the event name to the matchableAttrs
25473
25830
  }
25474
- _parseAction(value, isAssignmentEvent, sourceSpan) {
25831
+ _parseAction(value, sourceSpan) {
25475
25832
  const sourceInfo = (sourceSpan && sourceSpan.start || '(unknown').toString();
25476
25833
  const absoluteOffset = (sourceSpan && sourceSpan.start) ? sourceSpan.start.offset : 0;
25477
25834
  try {
25478
- const ast = this._exprParser.parseAction(value, isAssignmentEvent, sourceInfo, absoluteOffset, this._interpolationConfig);
25835
+ const ast = this._exprParser.parseAction(value, sourceInfo, absoluteOffset, this._interpolationConfig);
25479
25836
  if (ast) {
25480
25837
  this._reportExpressionParserErrors(ast.errors, sourceSpan);
25481
25838
  }
@@ -25510,6 +25867,26 @@ class BindingParser {
25510
25867
  this._reportError(report.msg, sourceSpan, ParseErrorLevel.ERROR);
25511
25868
  }
25512
25869
  }
25870
+ /**
25871
+ * Returns whether a parsed AST is allowed to be used within the event side of a two-way binding.
25872
+ * @param ast Parsed AST to be checked.
25873
+ */
25874
+ _isAllowedAssignmentEvent(ast) {
25875
+ if (ast instanceof ASTWithSource) {
25876
+ return this._isAllowedAssignmentEvent(ast.ast);
25877
+ }
25878
+ if (ast instanceof NonNullAssert) {
25879
+ return this._isAllowedAssignmentEvent(ast.expression);
25880
+ }
25881
+ if (ast instanceof PropertyRead || ast instanceof KeyedRead) {
25882
+ return true;
25883
+ }
25884
+ if (ast instanceof Binary) {
25885
+ return (ast.operation === '&&' || ast.operation === '||' || ast.operation === '??') &&
25886
+ (ast.right instanceof PropertyRead || ast.right instanceof KeyedRead);
25887
+ }
25888
+ return ast instanceof Conditional || ast instanceof PrefixNot;
25889
+ }
25513
25890
  }
25514
25891
  class PipeCollector extends RecursiveAstVisitor {
25515
25892
  constructor() {
@@ -26950,7 +27327,7 @@ class HtmlAstToIvyAst {
26950
27327
  }
26951
27328
  parseAssignmentEvent(name, expression, sourceSpan, valueSpan, targetMatchableAttrs, boundEvents, keySpan) {
26952
27329
  const events = [];
26953
- this.bindingParser.parseEvent(`${name}Change`, `${expression} =$event`, /* isAssignmentEvent */ true, sourceSpan, valueSpan || sourceSpan, targetMatchableAttrs, events, keySpan);
27330
+ this.bindingParser.parseEvent(`${name}Change`, expression, /* isAssignmentEvent */ true, sourceSpan, valueSpan || sourceSpan, targetMatchableAttrs, events, keySpan);
26954
27331
  addEvents(events, boundEvents);
26955
27332
  }
26956
27333
  reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
@@ -27784,7 +28161,9 @@ function prepareEventListenerParameters(eventAst, handlerName = null, scope = nu
27784
28161
  const implicitReceiverExpr = (scope === null || scope.bindingLevel === 0) ?
27785
28162
  variable(CONTEXT_NAME) :
27786
28163
  scope.getOrCreateSharedContextVar(0);
27787
- const bindingStatements = convertActionBinding(scope, implicitReceiverExpr, handler, 'b', eventAst.handlerSpan, implicitReceiverAccesses, EVENT_BINDING_SCOPE_GLOBALS);
28164
+ const bindingStatements = eventAst.type === 2 /* ParsedEventType.TwoWay */ ?
28165
+ convertAssignmentActionBinding(scope, implicitReceiverExpr, handler, 'b', eventAst.handlerSpan, implicitReceiverAccesses, EVENT_BINDING_SCOPE_GLOBALS) :
28166
+ convertActionBinding(scope, implicitReceiverExpr, handler, 'b', eventAst.handlerSpan, implicitReceiverAccesses, EVENT_BINDING_SCOPE_GLOBALS);
27788
28167
  const statements = [];
27789
28168
  const variableDeclarations = scope?.variableDeclarations();
27790
28169
  const restoreViewStatement = scope?.restoreViewStatement();
@@ -28352,7 +28731,7 @@ class TemplateDefinitionBuilder {
28352
28731
  // Generate Listeners (outputs)
28353
28732
  if (element.outputs.length > 0) {
28354
28733
  for (const outputAst of element.outputs) {
28355
- this.creationInstruction(outputAst.sourceSpan, Identifiers.listener, this.prepareListenerParameter(element.name, outputAst, elementIndex));
28734
+ this.creationInstruction(outputAst.sourceSpan, outputAst.type === 2 /* ParsedEventType.TwoWay */ ? Identifiers.twoWayListener : Identifiers.listener, this.prepareListenerParameter(element.name, outputAst, elementIndex));
28356
28735
  }
28357
28736
  }
28358
28737
  // Note: it's important to keep i18n/i18nStart instructions after i18nAttributes and
@@ -28395,6 +28774,7 @@ class TemplateDefinitionBuilder {
28395
28774
  this.allocateBindingSlots(value);
28396
28775
  propertyBindings.push({
28397
28776
  span: input.sourceSpan,
28777
+ reference: Identifiers.property,
28398
28778
  paramsOrFn: getBindingFunctionParams(() => hasValue ? this.convertPropertyBinding(value) : emptyValueBindInstruction, prepareSyntheticPropertyName(input.name))
28399
28779
  });
28400
28780
  }
@@ -28433,6 +28813,8 @@ class TemplateDefinitionBuilder {
28433
28813
  }
28434
28814
  }
28435
28815
  this.allocateBindingSlots(value);
28816
+ // Note: we don't separate two-way property bindings and regular ones,
28817
+ // because their assignment order needs to be maintained.
28436
28818
  if (inputType === 0 /* BindingType.Property */ || inputType === 5 /* BindingType.TwoWay */) {
28437
28819
  if (value instanceof Interpolation$1) {
28438
28820
  // prop="{{value}}" and friends
@@ -28443,6 +28825,7 @@ class TemplateDefinitionBuilder {
28443
28825
  // Collect all the properties so that we can chain into a single function at the end.
28444
28826
  propertyBindings.push({
28445
28827
  span: input.sourceSpan,
28828
+ reference: inputType === 5 /* BindingType.TwoWay */ ? Identifiers.twoWayProperty : Identifiers.property,
28446
28829
  paramsOrFn: getBindingFunctionParams(() => this.convertPropertyBinding(value), attrName, params)
28447
28830
  });
28448
28831
  }
@@ -28475,7 +28858,7 @@ class TemplateDefinitionBuilder {
28475
28858
  }
28476
28859
  });
28477
28860
  for (const propertyBinding of propertyBindings) {
28478
- this.updateInstructionWithAdvance(elementIndex, propertyBinding.span, Identifiers.property, propertyBinding.paramsOrFn);
28861
+ this.updateInstructionWithAdvance(elementIndex, propertyBinding.span, propertyBinding.reference, propertyBinding.paramsOrFn);
28479
28862
  }
28480
28863
  for (const attributeBinding of attributeBindings) {
28481
28864
  this.updateInstructionWithAdvance(elementIndex, attributeBinding.span, Identifiers.attribute, attributeBinding.paramsOrFn);
@@ -28508,7 +28891,9 @@ class TemplateDefinitionBuilder {
28508
28891
  }
28509
28892
  }
28510
28893
  const contextName = `${this.contextName}${contextNameSuffix}_${index}`;
28511
- const name = `${contextName}_Template`;
28894
+ // Note: For the unique name, we don't include an unique suffix, unless really needed.
28895
+ // This keeps the generated output more clean as most of the time, we don't expect conflicts.
28896
+ const name = this.constantPool.uniqueName(`${contextName}_Template`, /** alwaysIncludeSuffix */ false);
28512
28897
  // Create the template function
28513
28898
  const visitor = new TemplateDefinitionBuilder(this.constantPool, this._bindingScope, this.level + 1, contextName, this.i18n, index, name, this._namespace, this.fileBasedI18nSuffix, this.i18nUseExternalIds, this.deferBlocks, this.elementLocations, this.allDeferrableDepsFn, this._constants);
28514
28899
  // Nested templates must not be visited until after their parent templates have completed
@@ -28571,7 +28956,7 @@ class TemplateDefinitionBuilder {
28571
28956
  }
28572
28957
  // Generate listeners for directive output
28573
28958
  for (const outputAst of template.outputs) {
28574
- this.creationInstruction(outputAst.sourceSpan, Identifiers.listener, this.prepareListenerParameter('ng_template', outputAst, templateIndex));
28959
+ this.creationInstruction(outputAst.sourceSpan, outputAst.type === 2 /* ParsedEventType.TwoWay */ ? Identifiers.twoWayListener : Identifiers.listener, this.prepareListenerParameter('ng_template', outputAst, templateIndex));
28575
28960
  }
28576
28961
  }
28577
28962
  }
@@ -28825,7 +29210,9 @@ class TemplateDefinitionBuilder {
28825
29210
  for (const deferredDep of metadata.deps) {
28826
29211
  if (deferredDep.isDeferrable) {
28827
29212
  // Callback function, e.g. `m () => m.MyCmp;`.
28828
- const innerFn = arrowFn([new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(deferredDep.symbolName));
29213
+ const innerFn = arrowFn([new FnParam('m', DYNAMIC_TYPE)],
29214
+ // Default imports are always accessed through the `default` property.
29215
+ variable('m').prop(deferredDep.isDefaultImport ? 'default' : deferredDep.symbolName));
28829
29216
  // Dynamic import, e.g. `import('./a').then(...)`.
28830
29217
  const importExpr = (new DynamicImportExpr(deferredDep.importPath)).prop('then').callFn([innerFn]);
28831
29218
  dependencyExp.push(importExpr);
@@ -30332,9 +30719,9 @@ function compileDirectiveFromMetadata(meta, constantPool, bindingParser) {
30332
30719
  function createDeferredDepsFunction(constantPool, name, deps) {
30333
30720
  // This defer block has deps for which we need to generate dynamic imports.
30334
30721
  const dependencyExp = [];
30335
- for (const [symbolName, importPath] of deps) {
30722
+ for (const [symbolName, { importPath, isDefaultImport }] of deps) {
30336
30723
  // Callback function, e.g. `m () => m.MyCmp;`.
30337
- const innerFn = arrowFn([new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(symbolName));
30724
+ const innerFn = arrowFn([new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(isDefaultImport ? 'default' : symbolName));
30338
30725
  // Dynamic import, e.g. `import('./a').then(...)`.
30339
30726
  const importExpr = (new DynamicImportExpr(importPath)).prop('then').callFn([innerFn]);
30340
30727
  dependencyExp.push(importExpr);
@@ -32446,7 +32833,7 @@ function publishFacade(global) {
32446
32833
  * @description
32447
32834
  * Entry point for all public APIs of the compiler package.
32448
32835
  */
32449
- const VERSION = new Version('17.2.0-next.1');
32836
+ const VERSION = new Version('17.2.0-rc.1');
32450
32837
 
32451
32838
  class CompilerConfig {
32452
32839
  constructor({ defaultEncapsulation = ViewEncapsulation.Emulated, preserveWhitespaces, strictInjectionParameters } = {}) {
@@ -33947,9 +34334,11 @@ function compileComponentClassMetadata(metadata, deferrableTypes) {
33947
34334
  }
33948
34335
  const dynamicImports = [];
33949
34336
  const importedSymbols = [];
33950
- for (const [symbolName, importPath] of deferrableTypes) {
34337
+ for (const [symbolName, { importPath, isDefaultImport }] of deferrableTypes) {
33951
34338
  // e.g. `(m) => m.CmpA`
33952
- const innerFn = arrowFn([new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(symbolName));
34339
+ const innerFn =
34340
+ // Default imports are always accessed through the `default` property.
34341
+ arrowFn([new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(isDefaultImport ? 'default' : symbolName));
33953
34342
  // e.g. `import('./cmp-a').then(...)`
33954
34343
  const importExpr = (new DynamicImportExpr(importPath)).prop('then').callFn([innerFn]);
33955
34344
  dynamicImports.push(importExpr);
@@ -34012,7 +34401,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$5 = '12.0.0';
34012
34401
  function compileDeclareClassMetadata(metadata) {
34013
34402
  const definitionMap = new DefinitionMap();
34014
34403
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$5));
34015
- definitionMap.set('version', literal('17.2.0-next.1'));
34404
+ definitionMap.set('version', literal('17.2.0-rc.1'));
34016
34405
  definitionMap.set('ngImport', importExpr(Identifiers.core));
34017
34406
  definitionMap.set('type', metadata.type);
34018
34407
  definitionMap.set('decorators', metadata.decorators);
@@ -34108,7 +34497,7 @@ function createDirectiveDefinitionMap(meta) {
34108
34497
  const definitionMap = new DefinitionMap();
34109
34498
  const minVersion = getMinimumVersionForPartialOutput(meta);
34110
34499
  definitionMap.set('minVersion', literal(minVersion));
34111
- definitionMap.set('version', literal('17.2.0-next.1'));
34500
+ definitionMap.set('version', literal('17.2.0-rc.1'));
34112
34501
  // e.g. `type: MyDirective`
34113
34502
  definitionMap.set('type', meta.type.value);
34114
34503
  if (meta.isStandalone) {
@@ -34500,7 +34889,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
34500
34889
  function compileDeclareFactoryFunction(meta) {
34501
34890
  const definitionMap = new DefinitionMap();
34502
34891
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
34503
- definitionMap.set('version', literal('17.2.0-next.1'));
34892
+ definitionMap.set('version', literal('17.2.0-rc.1'));
34504
34893
  definitionMap.set('ngImport', importExpr(Identifiers.core));
34505
34894
  definitionMap.set('type', meta.type.value);
34506
34895
  definitionMap.set('deps', compileDependencies(meta.deps));
@@ -34535,7 +34924,7 @@ function compileDeclareInjectableFromMetadata(meta) {
34535
34924
  function createInjectableDefinitionMap(meta) {
34536
34925
  const definitionMap = new DefinitionMap();
34537
34926
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
34538
- definitionMap.set('version', literal('17.2.0-next.1'));
34927
+ definitionMap.set('version', literal('17.2.0-rc.1'));
34539
34928
  definitionMap.set('ngImport', importExpr(Identifiers.core));
34540
34929
  definitionMap.set('type', meta.type.value);
34541
34930
  // Only generate providedIn property if it has a non-null value
@@ -34586,7 +34975,7 @@ function compileDeclareInjectorFromMetadata(meta) {
34586
34975
  function createInjectorDefinitionMap(meta) {
34587
34976
  const definitionMap = new DefinitionMap();
34588
34977
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
34589
- definitionMap.set('version', literal('17.2.0-next.1'));
34978
+ definitionMap.set('version', literal('17.2.0-rc.1'));
34590
34979
  definitionMap.set('ngImport', importExpr(Identifiers.core));
34591
34980
  definitionMap.set('type', meta.type.value);
34592
34981
  definitionMap.set('providers', meta.providers);
@@ -34619,7 +35008,7 @@ function createNgModuleDefinitionMap(meta) {
34619
35008
  throw new Error('Invalid path! Local compilation mode should not get into the partial compilation path');
34620
35009
  }
34621
35010
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
34622
- definitionMap.set('version', literal('17.2.0-next.1'));
35011
+ definitionMap.set('version', literal('17.2.0-rc.1'));
34623
35012
  definitionMap.set('ngImport', importExpr(Identifiers.core));
34624
35013
  definitionMap.set('type', meta.type.value);
34625
35014
  // We only generate the keys in the metadata if the arrays contain values.
@@ -34670,7 +35059,7 @@ function compileDeclarePipeFromMetadata(meta) {
34670
35059
  function createPipeDefinitionMap(meta) {
34671
35060
  const definitionMap = new DefinitionMap();
34672
35061
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION));
34673
- definitionMap.set('version', literal('17.2.0-next.1'));
35062
+ definitionMap.set('version', literal('17.2.0-rc.1'));
34674
35063
  definitionMap.set('ngImport', importExpr(Identifiers.core));
34675
35064
  // e.g. `type: MyPipe`
34676
35065
  definitionMap.set('type', meta.type.value);
@@ -34703,5 +35092,5 @@ publishFacade(_global);
34703
35092
 
34704
35093
  // This file is not used to build this module. It is only used during editing
34705
35094
 
34706
- export { AST, ASTWithName, ASTWithSource, AbsoluteSourceSpan, ArrayType, ArrowFunctionExpr, AstMemoryEfficientTransformer, AstTransformer, Attribute, Binary, BinaryOperator, BinaryOperatorExpr, BindingPipe, Block, BlockParameter, BoundElementProperty, BuiltinType, BuiltinTypeName, CUSTOM_ELEMENTS_SCHEMA, Call, Chain, ChangeDetectionStrategy, CommaExpr, Comment, CompilerConfig, Conditional, ConditionalExpr, ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DYNAMIC_TYPE, DeclareFunctionStmt, DeclareVarStmt, DomElementSchemaRegistry, DynamicImportExpr, EOF, Element, ElementSchemaRegistry, EmitterVisitorContext, EmptyExpr$1 as EmptyExpr, Expansion, ExpansionCase, Expression, ExpressionBinding, ExpressionStatement, ExpressionType, ExternalExpr, ExternalReference, FactoryTarget$1 as FactoryTarget, FunctionExpr, HtmlParser, HtmlTagDefinition, I18NHtmlParser, IfStmt, ImplicitReceiver, InstantiateExpr, Interpolation$1 as Interpolation, InterpolationConfig, InvokeFunctionExpr, JSDocComment, JitEvaluator, KeyedRead, KeyedWrite, LeadingComment, Lexer, LiteralArray, LiteralArrayExpr, LiteralExpr, LiteralMap, LiteralMapExpr, LiteralPrimitive, LocalizedString, MapType, MessageBundle, NONE_TYPE, NO_ERRORS_SCHEMA, NodeWithI18n, NonNullAssert, NotExpr, ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan, ParseSpan, ParseTreeResult, ParsedEvent, ParsedProperty, ParsedPropertyType, ParsedVariable, Parser$1 as Parser, ParserError, PrefixNot, PropertyRead, PropertyWrite, R3BoundTarget, Identifiers as R3Identifiers, R3NgModuleMetadataKind, R3SelectorScopeMode, R3TargetBinder, R3TemplateDependencyKind, ReadKeyExpr, ReadPropExpr, ReadVarExpr, RecursiveAstVisitor, RecursiveVisitor, ResourceLoader, ReturnStatement, STRING_TYPE, SafeCall, SafeKeyedRead, SafePropertyRead, SelectorContext, SelectorListContext, SelectorMatcher, Serializer, SplitInterpolation, Statement, StmtModifier, TagContentType, TaggedTemplateExpr, TemplateBindingParseResult, TemplateLiteral, TemplateLiteralElement, Text, ThisReceiver, BoundAttribute as TmplAstBoundAttribute, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, DeferredBlock as TmplAstDeferredBlock, DeferredBlockError as TmplAstDeferredBlockError, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredTrigger as TmplAstDeferredTrigger, Element$1 as TmplAstElement, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, Icu$1 as TmplAstIcu, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, RecursiveVisitor$1 as TmplAstRecursiveVisitor, Reference as TmplAstReference, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, Template as TmplAstTemplate, Text$3 as TmplAstText, TextAttribute as TmplAstTextAttribute, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, UnknownBlock as TmplAstUnknownBlock, Variable as TmplAstVariable, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, Token, TokenType, TransplantedType, TreeError, Type, TypeModifier, TypeofExpr, Unary, UnaryOperator, UnaryOperatorExpr, VERSION, VariableBinding, Version, ViewEncapsulation, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, Xliff, Xliff2, Xmb, XmlParser, Xtb, _ParseAST, compileClassDebugInfo, compileClassMetadata, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, compileDeclareDirectiveFromMetadata, compileDeclareFactoryFunction, compileDeclareInjectableFromMetadata, compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileDeclarePipeFromMetadata, compileDirectiveFromMetadata, compileFactoryFunction, compileInjectable, compileInjector, compileNgModule, compilePipeFromMetadata, computeMsgId, core, createCssSelectorFromNode, createInjectableType, createMayBeForwardRefExpression, devOnlyGuardedExpression, emitDistinctChangesOnlyDefaultValue, encapsulateStyle, getHtmlTagDefinition, getNsPrefix, getSafePropertyAccessString, identifierName, isIdentifier, isNgContainer, isNgContent, isNgTemplate, jsDocComment, leadingComment, literal, literalMap, makeBindingParser, mergeNsAndName, output_ast as outputAst, parseHostBindings, parseTemplate, preserveWhitespacesDefault, publishFacade, r3JitTypeSourceSpan, sanitizeIdentifier, splitNsName, verifyHostBindings, visitAll };
35095
+ export { AST, ASTWithName, ASTWithSource, AbsoluteSourceSpan, ArrayType, ArrowFunctionExpr, AstMemoryEfficientTransformer, AstTransformer, Attribute, Binary, BinaryOperator, BinaryOperatorExpr, BindingPipe, Block, BlockParameter, BoundElementProperty, BuiltinType, BuiltinTypeName, CUSTOM_ELEMENTS_SCHEMA, Call, Chain, ChangeDetectionStrategy, CommaExpr, Comment, CompilerConfig, Conditional, ConditionalExpr, ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DYNAMIC_TYPE, DeclareFunctionStmt, DeclareVarStmt, DomElementSchemaRegistry, DynamicImportExpr, EOF, Element, ElementSchemaRegistry, EmitterVisitorContext, EmptyExpr$1 as EmptyExpr, Expansion, ExpansionCase, Expression, ExpressionBinding, ExpressionStatement, ExpressionType, ExternalExpr, ExternalReference, FactoryTarget$1 as FactoryTarget, FunctionExpr, HtmlParser, HtmlTagDefinition, I18NHtmlParser, IfStmt, ImplicitReceiver, InstantiateExpr, Interpolation$1 as Interpolation, InterpolationConfig, InvokeFunctionExpr, JSDocComment, JitEvaluator, KeyedRead, KeyedWrite, LeadingComment, Lexer, LiteralArray, LiteralArrayExpr, LiteralExpr, LiteralMap, LiteralMapExpr, LiteralPrimitive, LocalizedString, MapType, MessageBundle, NONE_TYPE, NO_ERRORS_SCHEMA, NodeWithI18n, NonNullAssert, NotExpr, ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan, ParseSpan, ParseTreeResult, ParsedEvent, ParsedProperty, ParsedPropertyType, ParsedVariable, Parser$1 as Parser, ParserError, PrefixNot, PropertyRead, PropertyWrite, R3BoundTarget, Identifiers as R3Identifiers, R3NgModuleMetadataKind, R3SelectorScopeMode, R3TargetBinder, R3TemplateDependencyKind, ReadKeyExpr, ReadPropExpr, ReadVarExpr, RecursiveAstVisitor, RecursiveVisitor, ResourceLoader, ReturnStatement, STRING_TYPE, SafeCall, SafeKeyedRead, SafePropertyRead, SelectorContext, SelectorListContext, SelectorMatcher, Serializer, SplitInterpolation, Statement, StmtModifier, TagContentType, TaggedTemplateExpr, TemplateBindingParseResult, TemplateLiteral, TemplateLiteralElement, Text, ThisReceiver, BoundAttribute as TmplAstBoundAttribute, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, DeferredBlock as TmplAstDeferredBlock, DeferredBlockError as TmplAstDeferredBlockError, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredTrigger as TmplAstDeferredTrigger, Element$1 as TmplAstElement, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, Icu$1 as TmplAstIcu, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, RecursiveVisitor$1 as TmplAstRecursiveVisitor, Reference as TmplAstReference, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, Template as TmplAstTemplate, Text$3 as TmplAstText, TextAttribute as TmplAstTextAttribute, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, UnknownBlock as TmplAstUnknownBlock, Variable as TmplAstVariable, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, Token, TokenType, TransplantedType, TreeError, Type, TypeModifier, TypeofExpr, Unary, UnaryOperator, UnaryOperatorExpr, VERSION, VariableBinding, Version, ViewEncapsulation, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, Xliff, Xliff2, Xmb, XmlParser, Xtb, compileClassDebugInfo, compileClassMetadata, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, compileDeclareDirectiveFromMetadata, compileDeclareFactoryFunction, compileDeclareInjectableFromMetadata, compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileDeclarePipeFromMetadata, compileDirectiveFromMetadata, compileFactoryFunction, compileInjectable, compileInjector, compileNgModule, compilePipeFromMetadata, computeMsgId, core, createCssSelectorFromNode, createInjectableType, createMayBeForwardRefExpression, devOnlyGuardedExpression, emitDistinctChangesOnlyDefaultValue, encapsulateStyle, getHtmlTagDefinition, getNsPrefix, getSafePropertyAccessString, identifierName, isIdentifier, isNgContainer, isNgContent, isNgTemplate, jsDocComment, leadingComment, literal, literalMap, makeBindingParser, mergeNsAndName, output_ast as outputAst, parseHostBindings, parseTemplate, preserveWhitespacesDefault, publishFacade, r3JitTypeSourceSpan, sanitizeIdentifier, splitNsName, verifyHostBindings, visitAll };
34707
35096
  //# sourceMappingURL=compiler.mjs.map