Compile errors when using async pipe with enableIvy & strictNullChecks
See original GitHub issueπ bug report
Affected Package
@angular/compiler-cli, @angular/angular
Is this a regression?
Yes, this works when Ivy is not used.
Description
The problem is the async pipe returns T | null. My understand is Ivy uses code similar to Partial<Pick<ChildComponent, βpropβ>> to check property bindings in templates. That allows undefined, but not null. Similarly, ngForOf doesnβt allow null, so that also doesnβt work with async.
This causes templates that previously compiled without errors to fail.
<!-- Must be built with strictNullChecks && enableIvy to see the errors below -->
<ul>
<!-- Type 'string[] | null' is not assignable to type 'string[] | Iterable<string> | undefined' -->
<li *ngFor="let n of array$ | async">{{n}}</li>
</ul>
<!-- Type 'number | null' is not assignable to type 'number | undefined' -->
<app-child [prop]="value$ | async"></app-child>
We use ngrx/store, so we use the async pipe a lot, and for the most part the observables from the store will always return a value immediately. But asyncβs return type means we have to deal with the null somehow even if it will never be emitted.
Iβve come up with several workarounds, but I donβt like any of them very much. And even if this is seen as behaving as intended, documentation should be updated (e.g. the example in https://angular.io/api/common/NgForOf#local-variables wouldnβt work with strictNullChecks && enableIvy).
Workarounds:
- Using non null assertions [prop]=(value$ | async)!"
I donβt like this since I donβt want developers to get used to throwing it around causally.
- Use || to provide a default, *ngFor=βlet i of (array$ | async) || []β
This works okay for the ngFor case, but doesnβt work for the prop case if one of the valid values is falsey, or if there is no good default value.
- Use ngIf with async, *ngIf=βvalue$ | async as valueβ
This is probably a good choice for the cases where the async pipe would legitimately be null, but is just needlessly cluttering template with ng-contianer or other elements in the cases where the async pipe will never return null. It also doesnβt work when the pipe will return a falsey value.
- Create a βpresentβ pipe that throws if itβs input is null/undefined and converts T | null to just T
Alternatively create a currentValue pipe that combines async & present, which could limit the input to Observable<T> (since the other options would just cause it to throw anyway).
This works but it makes the template expressions longer, and if itβs not built into @angular/angular its more for new devs on the team to understand.
π¬ Minimal Reproduction
Iβve created a repo showing this issue: https://github.com/james-schwartzkopf/test-template/blob/master/src/app/app.component.ts
π₯ Exception or Error
See Above
π Your Environment
Angular Version:
$ ng --version
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ β³ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 8.2.0
Node: 10.15.1
OS: win32 x64
Angular: 8.2.0
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Package Version
-----------------------------------------------------------
@angular-devkit/architect 0.802.0
@angular-devkit/build-angular 0.802.0
@angular-devkit/build-optimizer 0.802.0
@angular-devkit/build-webpack 0.802.0
@angular-devkit/core 8.2.0
@angular-devkit/schematics 8.2.0
@ngtools/webpack 8.2.0
@schematics/angular 8.2.0
@schematics/update 0.802.0
rxjs 6.4.0
typescript 3.5.3
webpack 4.38.0
Anything else relevant?
$ cat ./tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
],
"angularCompilerOptions": {
"enableIvy": true
}
}
$ cat ./tsconfig.json
{
"compileOnSave": false,
"compilerOptions": {
"strictNullChecks": true,
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}
Issue Analytics
- State:
- Created 4 years ago
- Reactions:12
- Comments:17 (11 by maintainers)
Top GitHub Comments
@klemenoslaj Ivyβs template type checker is far more advanced than the level of type checking that was previously done for templates, and in general the errors it produces are accurate. The problem is that they may be too accurate for someoneβs liking, especially because of cases like
ngFor
together with theasync
pipe where there has always been a type mismatch that has not been surfaced before because the type checking was not as accurate as it is becoming now.This is not the case. This issue in particular is about the
async
pipe, which includesnull
in its return type as it canβt differentiate based on the type ofObservable
if it will emit synchronously, or whether there may be a gap between the time of subscribing and the first value emission (in which case theasync
pipe has to returnnull
, because it doesnβt have any other information)The good news is that we recently introduced an internal template type checking option to effectively ignore typing issues around
null
andundefined
. A public facing option will be added shortly, so that developers can disable certain areas of the template type checker. This is basically @pshuryginβs suggestion in https://github.com/angular/angular/issues/32051#issuecomment-536285449.Disclaimer: I am not entitled to make official statements π Iβm just enthusiastic about Ivy and helping out whenever I can.
So only ngFor was changed? This will still be an issue for @Inputs that donβt include null in their type?