angular: Compile errors when using async pipe with enableIvy & strictNullChecks
š 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
}
}
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 12
- Comments: 17 (11 by maintainers)
@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?
Thanks @Splaktar, I agree. If any of you is still experiencing problems after updating to at least
9.0.0-next.10
, please open a new issue.If this issue is resolved by
NgForOf
getting fixed to accept thenull
in its typings that still means all the custom inputs we develop ourselves will have to acceptnull
by default? I mean, all of themā¦Do I even have to mention how cumbersome that is?or even worse, solving the issue within templates
Angular getās in our way here by preventing us writing our code the way we want it.
Question: There is probably a technical reason why
async
and some other pipes returnnull
, because if not, is it really needed?My reasoning: Subscribing to an observable directly will not give you null by default eitherā¦you will get nothing until the actual emission happens.
Iām having a similsar issues without using ngFor.
Iām using ng-select component like this:
<ng-select [(ngModel)]="city" [items]="cities$ | async">...</ng-select>
Here the cities$ have (non-nullable) type Observable<City[]> and [items] binding of the ng-select accepts (any[]|undefined), but async pipe changes type of cities$ into (City[]|null), and so we have a compile error.Another problem of the same category is with the number pipe, as it returns string|null. The code i have is
<div [matTooltip]="value|number">...</div>
where value is non-nullable number, but itās type is converted into string|null, which causes compiler error with matTooltip binding accepting string|undefined.