docs: add example to illustrate binding order differences in ivy (#36643)

Adds some examples and an explanation about the differences in binding order between Ivy and ViewEngine.

PR Close #36643
This commit is contained in:
crisbeto 2020-04-15 21:06:31 +02:00 committed by Matias Niemelä
parent 4480ba3e29
commit 39b043646b
2 changed files with 65 additions and 9 deletions

View File

@ -292,3 +292,59 @@ To fix this problem, we recommend binding to the `selected` property on the `<op
<option> <option>
</select> </select>
``` ```
{@a forward-refs-directive-inputs}
## Forward references to directive inputs accessed through local refs are no longer supported.
### Basic example of change
```ts
@Directive({
selector: '[myDir]',
exportAs: 'myDir'
})
export class MyDir {
@Input() message: string;
}
```
```html
{{ myDir.name }}
<div myDir #myDir="myDir" [name]="myName"></div>
```
In the View Engine runtime, the above code would print out the name without any errors.
In Ivy, the `myDir.name` binding will throw an `ExpressionChangedAfterItHasBeenCheckedError`.
### Background
In the ViewEngine runtime, directive input bindings and element bindings were executed in different stages. Angular would process the template one full time to check directive inputs only (e.g. `[name]`), then process the whole template again to check element and text bindings only (e.g.`{{ myDir.name }}`). This meant that the `name` directive input would be checked before the `myDir.name` text binding despite their relative order in the template, which some users felt to be counterintuitive.
In contrast, Ivy processes the template in just one pass, so that bindings are checked in the same order that they are written in the template. In this case, it means that the `myDir.name` binding will be checked before the `name` input sets the property on the directive (and thus it will be `undefined`). Since the `myDir.name` property will be set by the time the next change detection pass runs, a change detection error is thrown.
### Example of error
Assuming that the value for `myName` is `Angular`, you should see an error that looks like
```
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Angular'.
```
### Recommended fix
To fix this problem, we recommend either getting the information for the binding directly from the host component (e.g. the `myName` property from our example) or to move the data binding after the directive has been declared so that the initial value is available on the first pass.
*Before*
```html
{{ myDir.name }}
<div myDir #myDir="myDir" [name]="myName"></div>
```
*After*
```html
{{ myName }}
<div myDir [name]="myName"></div>
```

View File

@ -63,7 +63,7 @@ Please note that these constants are not meant to be used by 3rd party library o
* Foreign functions or foreign constants in decorator metadata aren't statically resolvable (previously, you could import a constant or function from another compilation unit, like a library, and use that constant/function in your `@NgModule` definition). * Foreign functions or foreign constants in decorator metadata aren't statically resolvable (previously, you could import a constant or function from another compilation unit, like a library, and use that constant/function in your `@NgModule` definition).
* Forward references to directive inputs accessed through local refs are no longer supported by default. * Forward references to directive inputs accessed through local refs are no longer supported by default. [details](guide/ivy-compatibility-examples#forward-refs-directive-inputs)
* If there is both an unbound class attribute and a `[class]` binding, the classes in the unbound attribute will also be added (previously, the class binding would overwrite classes in the unbound attribute). * If there is both an unbound class attribute and a `[class]` binding, the classes in the unbound attribute will also be added (previously, the class binding would overwrite classes in the unbound attribute).