angular: angular 9 / Ivy => async 'as' leads to error

🐞 bug report

Affected Package

https://github.com/mpalourdio/ng2

Is this a regression?

Yes, the previous version in which this bug was not present is angular8@latest.

Description

<div id="app" *ngIf="(applicationsList$ | async) as applicationsList">
    <app-search-filter [(applicationsList)]="applicationsList"></app-search-filter>
</div>

yarn start

Error : Property 'applicationsList' does not exist on type 'ChildComponent'. Did you mean 'applicationsList$'?

Disabling Ivy leads to

ERROR in Cannot assign value "$event" to template variable "applicationsList". Template variables are read-only.

🔬 Minimal Reproduction

🔥 Exception or Error


 Property 'applicationsList' does not exist on type 'ChildComponent'. Did you mean 'applicationsList$'?

Disabling Ivy leads to

ERROR in Cannot assign value "$event" to template variable "applicationsList". Template variables are read-only.

🌍 Your Environment

Angular Version:


Angular CLI: 9.0.0-rc.6
Node: 12.13.0
OS: linux x64

Angular: 9.0.0-rc.6
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.0-rc.6
@angular-devkit/build-angular     0.900.0-rc.6
@angular-devkit/build-optimizer   0.900.0-rc.6
@angular-devkit/build-webpack     0.900.0-rc.6
@angular-devkit/core              9.0.0-rc.6
@angular-devkit/schematics        9.0.0-rc.6
@ngtools/webpack                  9.0.0-rc.6
@schematics/angular               9.0.0-rc.6
@schematics/update                0.900.0-rc.6
rxjs                              6.5.3
typescript                        3.6.4
webpack                           4.41.2


EDIT (by gkalpak)

Adding the conclusions from https://github.com/angular/angular/issues/34405#issuecomment-566134215 (after the discussion below) here for visibility:

The issue is that this breaking change is not (clearly) documented, so let’s keep this open to track that 😃

Action items:

  • Figure out what the breaking change is exactly (incl. how template variables created via as are different from other template variables).
  • Document that.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 1
  • Comments: 21 (10 by maintainers)

Most upvoted comments

hello in my case my mistake was because I had this like this:

<mat-option *ngFor="let terminal of terminals" [(value)]="terminal">{{terminal.Name}}</mat-option>

What I did to solve was to change the value as follows:

<mat-option *ngFor="let terminal of terminals" value="{{terminal}}">{{terminal.Name}}</mat-option>

hola en mi caso mi error era por que tenia esto asi :

<mat-option *ngFor="let terminal of terminals"  [(value)]="terminal">{{terminal.Name}}</mat-option>

lo que hice para solventar fue cambiar el value a la siguiente manera:

<mat-option *ngFor="let terminal of terminals"  value="{{terminal}}">{{terminal.Name}}</mat-option>

The error in Ivy was incorrect, this has been resolved in #34339 (which just missed the rc.6 release).

Other than that: what @gkalpak said 😃

Looks like it was made intentionally according to Ivy Compatibility Guide

It is now an error to assign values to template-only variables like item in ngFor=“let item of items” (previously, the compiler would ignore these assignments).

The issue is that this breaking change is not (clearly) documented, so let’s keep this open to track that 😃

Action items:

  • Figure out what the breaking change is exactly (incl. how template variables created via as are different from other template variables).
  • Document that.

That’s really clear for me! Thanks a lot for these great explanation, and for taking time to investigate. So far, again, that’s really a quick and dirty project and I wanted to give feedback on the migration path.

Feel free to close this issue, as it’s really not an issue ('cause I am the issue :p).

Thanks again!

I created this StackBlitz project and it does indeed work with v8 💥 Maybe the variables created via as were treated differently than other template-only variables in v8.

So, this sounds like a breaking change that needs to be documented. @JoostK, do you know what’s the case for template variables created via as in v8 vs v9?

Apart the very poor code quality, what is fundamentally wrong here?

@mpalourdio: This seems like a big anti-pattern (and I hope it is not as common as you say 😅).

The main issue I see is that the [(...)] binding indicates a two-way-binding relation. Typically, the expectation is that the value is passed into the child-component (the [...] part of the binding) and the child component reacts to this.

So, my expectation when I first looked at the component (and the reason I immediately thought “this can’t be right” 😁) was that applicationList goes into the child-component (due to the [...] part of the binding), then gets filtered inside the child-component and emitted back, the filtered list gets assigned to the template applicationList variables (via the (...) part of the binding), then the filtered applicationList gets back into the child-component (via the [...] part of the binding), where it gets filtered again and emitted back, etc.

The only reason the component works in this case is that it uses the [...] binding to initialize its internal state and does not react to changes in the input value. This is, of course, an implementation detail of the child-component and the consumer (parent component) shouldn’t have to know about it. Hence, from the consumer’s perspective, using [(...)] looks very unintuitive.

In addition, you open yourself up to all sorts of subtle bugs, such as:

  • If applicationList$ emits another array, the search-filter component will still be using the original list, giving incorrect results.

Of course, you can work around them, but the point is that the parent-component behavior may change due to the implementation of a child-component and detailed knowledge of the child-component’s implementation is needed in order to make the parent-component/template work as intended.

It is not clear what you are trying to achieve (i.e. what the expected/ideal behavior would be) with [(applicationsList)]="applicationsList".

Just to be clear, the error is not related to async ... as (as mentioned in the title), but to the following bit of code:

<app-search-filter [(applicationsList)]="applicationsList"></app-search-filter>

This is syntactic sugar for a two-way binding. It is equivalent to:

<app-search-filter [applicationsList]="applicationsList" (applicationsListChange)="applicationsList = $event"></app-search-filter>

It’s this second part ((applicationsListChange)="applicationsList = $event") that seems to be causing the error. It is not clear what you are trying to achieve by setting the template applicationList variable to the filtered result of itself. Maybe the example you gave is contrived and does not reflect the actual usecase. Could you, please, clarify the usecase?

(Finally, as mentioned in the guide, the compiler would previouly ignore the assignments, so this change will not break something that used to work before. It will only turn silent failing at runtime to hard failing at compile time.)

@mpalourdio I am amazed this ever worked!