Consider a template with a context variable `a`:
```
<ng-template let-a>{{this.a}}</ng-template>
```
t push -fAn interpolation inside that template to `this.a` should intuitively read the class variable `a`. However, today, it refers to the context variable `a`, both in the TCB and the generated code.
In this commit, the above interpolation now refers to the class field `a`.
BREAKING CHANGE: `this.foo` property reads no longer refer to template context variables. If you intended to read the template variable, do not use `this.`.
Fixes#55115
PR Close#55183
Finalizes compiler implementation of the new `hydrate` triggers by:
* Reworking the logic that was depending on the `hydrateSpan` to distinguish hydrate triggers from non-hydrate triggers.
* Fixing that the `hydrate when` trigger didn't have a `hydrateSpan`.
* Adding an error if a parameter is passed into a `hydrate` trigger.
* Add an error if other `hydrate` triggers are used with `hydrate never`.
* Replacing the `prefetch` and `hydrate` flags in the template pipeline with a `modifiers` field.
* Fixing an error that was being thrown when reifying `hydrate` triggers in the pipeline.
* Adding quick info support for the `hydrate` keyword in the language service.
* Adding some tests for the new logic.
PR Close#57831
This commit updates a directive mock instance to include an extra field that a compiler code was expecting, which caused issues while processing elements with local refs and exported directives.
PR Close#57537
When disabling `i18nPreserveSignificantWhitespaceForLegacyExtraction` I was looking at a test case with ICU messages containing leading and trailing whitespace:
```angular
<div i18n>
{apples, plural, =other {I have many apples.}}
</div>
```
This would historically generate two messages:
```javascript
const MSG_TMP = goog.getMsg('{apples, plural, =other {I have many apples.}}');
const MSG_FOO = goog.getMsg(' {$ICU} ', { 'ICU': MSG_TMP });
```
But I found that I was getting just one message:
```javascript
const MSG_TMP = goog.getMsg(' {apples, plural, =other {I have many apples.}} ');
```
This is arguably an improvement, but changed the messages and message IDs, which isn't desirable with this option. I eventually traced this back to the `isIcu` initialization in [`i18n_parser.ts`](/packages/compiler/src/i18n/i18n_parser.ts):
```typescript
const context: I18nMessageVisitorContext = {
isIcu: nodes.length == 1 && nodes[0] instanceof html.Expansion,
// ...
};
```
[`_I18nVisitor.prototype.visitExpansion`](/packages/compiler/src/i18n/i18n_parser.ts) uses this to decide whether or not to generate a sub-message for a given ICU expansion:
```typescript
if (context.isIcu || context.icuDepth > 0) {
// Returns an ICU node when:
// - the message (vs a part of the message) is an ICU message, or
// - the ICU message is nested.
const expPh = context.placeholderRegistry.getUniquePlaceholder(`VAR_${icu.type}`);
i18nIcu.expressionPlaceholder = expPh;
context.placeholderToContent[expPh] = {
text: icu.switchValue,
sourceSpan: icu.switchValueSourceSpan,
};
return context.visitNodeFn(icu, i18nIcu);
}
// Else returns a placeholder
// ICU placeholders should not be replaced with their original content but with the their
// translations.
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
const phName = context.placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
context.placeholderToMessage[phName] = this.toI18nMessage([icu], '', '', '', undefined);
const node = new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
return context.visitNodeFn(icu, node);
```
Note that `isIcu` is the key condition between these two cases and depends on whether or not the ICU expansion has any siblings. The introduction of `WhitespaceVisitor` to `I18nMetaVisitor` trims insignificant whitespace, including empty text nodes not adjacent to an ICU expansion (from [`WhitespaceVisitor.prototype.visitText`](/packages/compiler/src/ml_parser/html_whitespaces.ts)):
```typescript
const isNotBlank = text.value.match(NO_WS_REGEXP);
const hasExpansionSibling =
context && (context.prev instanceof html.Expansion || context.next instanceof html.Expansion);
if (isNotBlank || hasExpansionSibling) {
// Transform node by trimming it...
return trimmedNode;
}
return null; // Drop node which is empty and has no ICU expansion sibling.
```
`hasExpansionSibling` was intended to retain empty text nodes leading or trailing an ICU expansion, however `context` was `undefined`, so this check failed and the leading / trailing text nodes were dropped. This resulted in trimming the ICU text by dropping the leading / trailing whitespace nodes. Having only a single ICU expansion with no leading / trailing text nodes caused `_I18nVisitor` to initialize `isIcu` incorrectly and caused it to generate one message instead of two.
`WhitespaceVisitor` is supposed to get this context from `visitAllWithSiblings`. So the fix here is to make sure `WhitespaceVisitor` is always visited via this function which provides the required context. I updated all usage sites to make sure this context is use consistently and implemented the `WhitespaceVisitor.prototype.visit` method to throw when the context is missing to make sure we don't encounter a similar mistake in the future.
Unfortunately this broke one compliance test. Specifically the [`icu_logic/icu_only.js`](/home/douglasparker/Source/ng/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/icu_only.js) test which changed from generating:
```javascript
function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵi18n(0, 0);
}
// ...
}
```
To now generating:
```javascript
function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵtext(0, " ");
i0.ɵɵi18n(1, 0);
i0.ɵɵtext(2, "\n");
}
// ...
}
```
This test uses the default value `preserveWhitespaces: false` (`i18nPreserveSignificantWhitespaceForLegacyExtraction` should not affect compiled JS output, we already retain significant whitespace there). So what this indicates to me is that ICU logic is already broken because it's not preserving significant whitespace in this case. My change is probably a bug fix, but one which would affect the compiled runtime, which is not in scope here. The root cause is because using `visitAllWithSiblings` everywhere means the context is retained correctly in this case and the whitespace is leading/trailing an ICU message, therefore it is retained per the logic of `WhitespaceVisitor.prototype.visitText` I mentioned eariler.
To address this, I left one usage of `WhitespaceVisitor` using `html.visitAll` instead of `visitAllWithSiblings` to retain this bug. I has to lossen the assertion I put in `WhitespaceVisitor.prototype.visit` to make this possible, but it should still throw by default when misused, which is the important part.
PR Close#56507
This configures whether or not to preserve whitespace content when extracting messages from Angular templates in the legacy (View Engine) extraction pipeline.
This includes several bug fixes which unfortunately cannot be landed without changing message IDs in a breaking fashion and are necessary to properly trim whitespace. Instead these bug fixes are included only when the new flag is disabled.
PR Close#56507
This commit adds an internal util method that allows to detect:
* which selectors are matching nodes in a template
* which pipes are present in a template
Both directives and pipes are split into 2 buckets: eagerly used and the ones that might potentially be defer-loaded.
PR Close#57466
Enables the new `@let` syntax by default.
`@let` declarations are defined as:
1. The `@let` keyword.
2. Followed by one or more whitespaces.
3. Followed by a valid JavaScript name and zero or more whitespaces.
4. Followed by the `=` symbol and zero or more whitespaces.
5. Followed by an Angular expression which can be multi-line.
6. Terminated by the `;` symbol.
Example usage:
```
@let user = user$ | async;
@let greeting = user ? 'Hello, ' + user.name : 'Loading';
<h1>{{greeting}}</h1>
```
Fixes#15280.
PR Close#56715
Introduces a new `LetDeclaration` into the Render3 AST, simiarly to the HTML AST, and adds an initial integration into the various visitors.
PR Close#55848
Two-way bindings are meant to represent a property binding to an input and an event binding to an output, e.g. `[(ngModel)]="foo"` represents `[ngModel]="foo" (ngModelChange)="foo = $event"`. Previously due to a quirk in the template parser, we accidentally supported unassignable expressions in two-way bindings.
In #54154 the quirk was fixed, but we kept support or some common expression because of internal usages. Now the internal usages have been cleaned up so the backwards-compatibility code can be deleted.
Externally a migration was added in #54630 that will automatically fix any places that depended on the old behavior.
BREAKING CHANGE:
Angular only supports writable expressions inside of two-way bindings.
PR Close#55342
Based on some internal feedback, these changes add validations to prevent cases where the `@for` loop variable name is the same as one of the built-in context variables, or when one of the context variables is aliased to the same name as the item.
PR Close#55045
Currently when aliasing a `for` loop variable with `let`, we replace the variable's old name with the new one. Since users have found this to be confusing, these changes switch to a model where the variable is available both under the original name and the new one.
Fixes#52528.
PR Close#54942
`TemplateDefinitionBuilder` is the legacy template compiler, and was replaced by Template Pipeline as the default in v17.3.
This PR attempts to delete `TemplateDefinitionBuilder`, `ExpressionConverter`, and various helpers (i18n context, style builder, property visitors, etc).
Consider this a first pass: a lot of code has not yet been deleted (e.g. old TDB-specific test cases), and I'm sure I have missed additional helper code.
PR Close#54757
In one of the earlier commits, the logic that appends `=$event` before parsing two-way bindings was removed and some validation was added to prevent unassignable expressions from being used. This ended up being problematic, because previously the parser was incorrectly allowing some invalid expressions which users came to depend on. For example, it transformed `[(value)]="a && a.b"` to `a && (a.b = $event)`.
These changes add some special cases for the common breakages that came up during the TGP.
PR Close#54154
Currently the listener side two-way listeners are parsed by appending `=$event` to the raw expression. This is problematic, because:
1. It can interfere with other expressions (see #37809).
2. It can lead to confusing error messages because users will see code that they didn't write.
3. It doesn't allow us to further manipulate the expression.
These changes remove the logic that appends `=$event` to resolve the issue. There's also some new logic that checks the expression after it has been parsed to ensure that the result is an assignable expression.
Subsequent commits will update the code that emits the expression to add back the `$event` assignment where it's needed.
PR Close#54154
During the template parsing stage two-way bindings are split up into a property and event binding. All the downstream code treats these binding the same as their one-way equivalents. For some future work we'll have to distinguish between the two so these changes update the `BoundElementProperty.type` and `ParsedEvent.type` to include a `TwoWay` type. All existing call-sites have been updated to treat `TwoWay` the same as `Property`/`Regular`, but more specialized logic will be added in the future.
PR Close#54065
These changes expose the `ngContentSelectors` and `preserveWhitespaces` metadata to the TCB so they can be used in the next commit to implement a new diagnostic.
PR Close#53190
When blocks were initially implemented, they were represented as containers in the i18n AST. This is problematic, because block affect the structure of the message.
These changes introduce a new `BlockPlaceholder` AST node and integrate it into the i18n pipeline. With the new node blocks are represented with the `START_BLOCK_<name>` and `CLOSE_BLOCK_<name>` placeholders.
PR Close#52958
These changes expose the `ngContentSelectors` and `preserveWhitespaces` metadata to the TCB so they can be used in the next commit to implement a new diagnostic.
PR Close#52726
Fixes that our regex for parsing time values in defer blocks didn't allow for decimals. This isn't relevant for times in milliseconds, but it can be convenient to write something like `on timer(1.5s)`.
PR Close#52433
Adds some logic to skip over comments when resolving implicit `@defer` block triggers. This currently isn't a problem since we don't capture comments by default, but it may come up if we start capturing comments.
PR Close#52449
Updates the Ivy AST to allow for `@switch` blocks to capture nested blocks that are not `@case` and `@default`. These blocks will be used for autocompletion in the language service.
These changes also update the logic for `@switch` and `@if` blocks so that they produce an AST node even if there are errors. The errors will still be surfaced to users, but producing AST nodes allows us to recover parts of the expression later if necessary.
PR Close#52136
Two key refactors to enable deeper language service support for blocks:
(1) We now generate accurate source spans for the various block types. Additionally, all the top-level source spans for a block are now *inclusive* of all the connected or descending blocks. This helps the language service visit connected blocks.
(2) The language service's template visitor was previously skipping over the AST nodes corresponding to several block types. We are now careful to visit all such nodes.
PR Close#52038
Adds an `UnknownBlock` node to the Ivy AST to represent blocks that haven't been recognized by the compiler. This will make it easier to integrate blocks into the language service.
PR Close#52047
Enables the new `@` block syntax by default by removing the `enabledBlockTypes` flags. There are still some internal flags that allow special use cases to opt out of the block syntax, like during XML parsing and when compiling older libraries (see #51979).
PR Close#51994
Adds support for defining `viewport`, `interaction` and `hover` triggers with no parameters. If the framework encounters such a case, it resolves the trigger to the root element of the `@placeholder` block. Triggers with no parameters have the following restrictions:
1. They have to be placed on an `@defer` block that has an `@placeholder`.
2. The `@placeholder` can only have one root node.
3. The root placeholder node has to be an element.
PR Close#51922
Switches the syntax for blocks from `{#block}{/block}` to `@block {}` based on the feedback from the community.
Read more about the decision-making process in our blog: https://blog.angular.io/meet-angulars-new-control-flow-a02c6eee7843
The existing block types changed in the following ways:
**Conditional blocks:**
```html
<!-- Before -->
{#if cond}
Main content
{:else if otherCond}
Else if content
{:else}
Else content
{/if}
<!-- After -->
@if (cond) {
Main content
} @else if (otherCond) {
Else if content
} @else {
Else content
}
```
**Deferred blocks**
```html
<!-- Before -->
{#defer when isLoaded}
Main content
{:loading} Loading...
{:placeholder} <icon>pending</icon>
{:error} Failed to load
{/defer}
<!-- After -->
@defer (when isLoaded) {
Main content
} @loading {
Loading...
} @placeholder {
<icon>pending</icon>
} @error {
Failed to load
}
```
**Switch blocks:**
```html
<!-- Before -->
{#switch value}
{:case 1}
One
{:case 2}
Two
{:default}
Default
{/switch}
<!-- After -->
@switch (value) {
@case (1) {
One
}
@case (2) {
Two
}
@default {
Default
}
}
```
**For loops**
```html
<!-- Before -->
{#for item of items; track item}
{{item.name}}
{:empty} No items
{/for}
<!-- After -->
@for (item of items; track item) {
{{item.name}}
} @empty {
No items
}
```
PR Close#51891
Adds support for template type checking inside `for` blocks. It is implemented by generating a JS `for...of` statement inside the TCB. The various loop variables (e.g. `$index`) are implemented by declaring a local number variable.
PR Close#51690
Adds support for template type checking inside `if` blocks. It is implemented by generating a JS `if` statement inside the TCB which allows us to do type narrowing of the expression. The `as` parameter is implemented by declaring a variable inside the `if` statement.
PR Close#51690
Adds a utility to the `BoundTarget` that helps with resolving which element a deferred block is pointing to. We need a separate method for this, because deferred blocks have some special logic for where the trigger can be located.
PR Close#51816
When the `TargetBinder` was written, the only embedded-view-based nodes were templates, but now we have `{#if}`, `{#switch}` and `{#defer}` which have similar semantics. These changes rework the binder to account for the new nodes.
PR Close#51816
When `preserveWhitespaces` is enabled, `switch` blocks can end up with content inside their main block due to the indentation that is usually used for the nested cases. This was tripping up the validation that doesn't allow content inside the main block of `switch`.
These changes update the validation to ignore empty text nodes.
PR Close#51570