-
Notifications
You must be signed in to change notification settings - Fork 27k
Description
Which @angular/* package(s) are the source of the bug?
common
Is this a regression?
Yes
Description
It seems the saga of | null and its infectious nature in TypeScript code continues in Angular 15...
Current problem, which triggered the creation of this issue. In versions of Angular prior to 15, it used to be possible to pass any value to *ngTemplateOutlet. This included null, OR undefined meaning if you did not have a value, then the template did not render. In Angular 15, it seems the type possibilities have been NARROWED by the explicit use of ngTemplateOutlet: TemplateRef<any> | null. This narrowing of allowed types is forcing developers to explicitly deal with this non-allowance of undefined, which has become a very significant problem with Angular.
First off, some basic background. I've shared these thoughts a couple of years ago with regards to the async pipe and IT'S expansion of types by ADDING | null which imposes significant typing issues with strict typescript compilation. Due to the ongoing issue with AsyncPipe adding | null to its output, I've switched entirely to the @rx-anglular/template library's PushPipe which seems to handle output typing better with seamless support for null and undefined without forcing the developer to add | null to all of their types.
To lay the groundwork, the nature of null vs. undefined:
- In JavaScript/TypeScript
nullis not the same asundefined, even though they sometimes behave similarly - In JavaScript,
undefinedis explicitly the only supported option that triggers things like default parameter values, or optional parameters, non-rendering of properties when serialized with JSON.stringigy, etc. - In JavaScript,
nulldoes NOT trigger things like default parameter values, it is serialized with JSON.stringify, etc. - When considering
null, it should be thought of as an explicit VALUE that represents nothingness. - When considering
undefined, it should be thought of as an explicit STATE of being that represents non-existence.
In other words, null is a value that can be passed and set that represents a current value of "nothingness" for any value, undefined represents non-existence and triggers language features that rely on this explicit state of non-existence.
When it comes to inputs, supporting only null but not undefined is overtly ignoring a very critical feature of the underlying language platform, and forcing developers to explicitly deal with the expectation of "something or null" when previously, we simply didn't have to bother or think about it...if we did not have an existing thing, stuff just worked, and if we DID have some thing, stuff still just worked. When it comes to outputs, by explicitly using | null rather than | undefined again forces developers to deal with the null possibility. This increases the complexity of our code. This is not "natural" or "idiomatic" in JavaScript. If you get something that is undefined, it is simply undefined, and natural idiomatic language features can be used to handle that state of non-existence seamlessly. On the other hand, if something is null, then you MUST deal with the possibility of null...which means infesting ever growing swaths of your codebase with | null attachments to your existing typing, explicit null checks and added complexity to your code.
The insistence by the Angular team on forcing their developers to deal with null is sadly making this framework, under strict typing rules, harder and harder to work with. Something really needs to be done here to support undefined properly, broadly, so that developers can seamlessly work with natural, idomatic language features of JavaScript and TypeScript, and NOT have to explicitly tack on | null or add explicit null checks and fallbacks throughout their code bases in order to interact with Angular features. When it comes to INPUTs, such as the ngTemplateOutlet directive, this should be very easy. Expanding the allowed types to include | undefined in addition to the current TemplateRef<any> | null should be non-breaking in every case.
In the case of return types...as indicated by the commentary on my previous issue linked above about the AsyncPipe and its issues with | null, switching from null to undefined is clearly not as simple. IMO, the use of null is not idiomatic in TypeScript, whereas use of undefined is in both JavaScript and TypeScript, and many language features explicitly require an undefined value rather than null to be triggered.
I had hoped this would be resolved with Angular 16, however at the current time it appears the | null issue is still rather endemic within the platform. A major version is the opportune time to deprecate signatures that force developers to add support for null in their code, while introducing overloads that support undefined...opening the door for removal of | null signatures in an eventual future version. I'm hoping that this issue can gain some traction before Angular 18 is released, and hopefully some of these issues can be resolved with a switch towards primarily supporting the idiomatic capabilities of TypeScript and JavaScript, rather than an odd, notably more complex language feature like null.
Please provide a link to a minimal reproduction of the bug
Use NgTemplateOutlet in any app, pass in an undefined, in Angular 15
Please provide the exception or error you saw
Type TemplateRef<Thing> | undefined is not assignable to type TemplateRef<any> | null
Please provide the environment you discovered this bug in (run ng version)
Angular CLI: 15.2.9
Node: 18.16.1
Package Manager: npm 9.5.1
OS: darwin x64
Angular: 15.2.9
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, google-maps, language-service, material
... platform-browser, platform-browser-dynamic, router
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1502.9
@angular-devkit/build-angular 15.2.9
@angular-devkit/core 15.2.9
@angular-devkit/schematics 15.2.9
@schematics/angular 15.2.9
rxjs 7.8.1
typescript 4.8.4
Anything else?
As Angular enforces tighter and tighter typing restrictions, the use of | null is becoming more and more problematic. I originally noted this issue back in October, 2021, when I opened the issue linked above about the AsyncPipe with Angular version 12. There have been four versions of Angular since then, and the use of | null seems to only have become even more extensive and problematic, with it appearing in template outlets, strictly typed forms (which has been rather devastating...I generally don't like to use strictly typed forms because using them has become so complicated, in large part but not limited to their enforcing | null in return values), and many other places.
( I AM glad to see that the introduction of the Signal type to Angular did not also include another | null in its signature. That is a welcome improvement!)
The use of null and null checks in JavaScript and TypeScript code is considered a sign of an UNHEALTHY codebase (some good info on this in Refactoring Typescript by James Hicky, an MVP on the TypeScript team at Microsoft.)
I'm a HUGE fan of Angular. It is my preferred framework. Never liked React, don't enjoy using it. I find the mutation-based change detection of Vue frustrating. However I've been rather dismayed at how stricter typing with Angular seems to have made my life harder, often a lot harder, rather than easier. I think most of that, comes from the use of null rather than the idiomatic undefined...and sadly, I think that the extensive and growing use of null in the Angular codebase may indeed be a sign of poor health of the framework.
In desperation, I implore the Angular team to seriously consider the impact of forcing their end developers to deal with null from Angular, and at the very least consider, wherever possible, adding in overloaded method signatures capable of handling undefined as input (even if null remains supported). While I understand the potential breaking change that switching from | null to | undefined for return values may impose, in the long run, I think it will make using Angular a more pleasant experience, and I believe that is critical for the long term health of Angular and its broader ecosystem.
Thank you for getting this far!