Open mgechev opened 4 years ago
Just to check, does this cover the case of array-index optional chaining (arr?.[0]
) in templates? I get an error when I try to use that today, though I'm not sure if it's a problem Angular itself or just the language service.
It would be very interesting to use optional chaining in arrays, following the TS spec:
In Angular Templates, arr?.[0] causes a:
zone-evergreen.js:659 Unhandled Promise rejection: Template parse errors: Parser Error: Unexpected token [, expected identifier or keyword at column 4 in [arr?.[0]] in ng:///ProdutoCadComponent/template.html@996:64
Arrays/Indexes are safely navigated in templates but not in the way one would expect:
In TypeScript: foo?.bar?.[0]?.quxx
In Angular Templates: foo?.bar[0]?.quxx
See https://github.com/angular/angular/issues/13254
and https://github.com/angular/angular/commit/f31c9470fae06adc69b2b665773bfc0a8a10d10e
TL;DR: Angular Templates resolves to null
in safe navigation and TypeScript resolves to undefined
in optional chaining.
Consider the following:
Component Typescript
public preferences: {email: string};
public getPreferencesEmail() {
return this.preferences?.email;
}
public ngOnInit() {
this.preferencesService.subscribe(preferences =>
(this.preferences = preferences));
}
Example A HTML
<div *ngIf="preferences?.email === undefined; else emailInput">Loading...</div>
<ng-template #emailInput>
<input id="email" [value]="preferences.email">
</ng-template>
Example B HTML
<div *ngIf="getPreferencesEmail() === undefined; else emailInput">Loading...</div>
<ng-template #emailInput>
<input id="email" [value]="getPreferencesEmail()">
</ng-template>
In Example A, the loading div does not show because Angular Templates evaluates the safe-navigation to null.
In Example B, the loading div does show because TypeScript evaluates the optional chaining to undefined.
If undefined means "Loading", null means "Not set by user" and anything else is "a user-set value"; then angular's safe navigation introduces a bug in our code.
This can be even more confusing given that preferences
is undefined
and the Angular's safe navigation resolves preferences?.email
to null
.
Arrays/Indexes are safely navigated in templates but not in the way one would expect: In TypeScript:
foo?.bar?.[0]?.quxx
In Angular Templates:foo?.bar[0]?.quxx
See #13254 and f31c947
The problem with angular's syntax is that there's no way to check that the array isn't undefined or null. Yes, what you show will check to see if the array has that indexed item, but if I want to know that the property is in fact initialized, I have to do
foo?.bar && foo?.bar[0]?
It would be much better to use optional chaining and just use
foo?.bar?.[0]?
Another thing to note: since TypeScript 3.9 non-null-assertions don't end the optional chain
foo?.bar!.baz;
// ts@<3.9
(foo?.bar).baz; // asserts that `foo?.bar` is non-null - which obviously doesn't make sense
// ts@>=3.9
(foo?.bar.baz); // asserts that if `foo` is non-null, its `bar` property will also be non-null
Hi,
For me it shows this:
Parser Error: Unexpected token [, expected identifier or keyword at column 33 in [
{{userItemModel?.item?.priceList?.[0].sellerUrl}}
] in
In my case
this.property?.UserProperties?.[0].id
Display error:
Template parse errors: Parser Error: Unexpected token [, expected identifier or keyword at column 32 in [this.property?.UserProperties?.[0].id] in bmp.component.html@219:84
Arrays/Indexes are safely navigated in templates
bar[0]?.quxx
may be safely navigated runtime. But compile time it's not.
Both the language service and the compiler errors on the above.
[foo]="bar[0]?.quxx"
~~~~~~
Error: Object is possibly 'null'. ngtsc(2531)
This makes the "safe navigated in templates" irrelevant as the compiler/type checker doesn't allow it.
The proper ES syntax (bar?.[0]?.quxx
) also makes it clear what object can be null|undefined
.
In the above example both bar
and bar[0]
can be null|undefined
.
This leads to very inconsistent behavior. I used the exact same code both in *ngIf and if in code: foo?.bar <= 0 If foo is null, the result is true in templates and false in code. 🙈
@petebacondarwin since you're eyes-on here, and it's been quite some time since this issue was really active, do you think you could get a status for this from the team? Maybe assign a priority?
@thw0rted - we definitely want to move to ECMAScript compliant behaviour, but since this would be a breaking change, we would also need to consider how to migrate users and it would have to happen in a major release. This is unlikely to happen for v13 now, but I will raise it in our next framework sync up meeting in two weeks.
since there are finite rules around how angular does optional chaining, it would be a relatively easy regex replace rule for the most part, right? i know, it's always easy to say something's easy :)
I think it is definitely possible to write a migration for these cases, but I don't think it is trivial. So we would need to plan the work alongside prioritising other work.
This is being tracked in the aggregate issue of #43485 for which we need to create a project proposal.
I tried to write a webpage for OBS Studio. But the optional chaining is not supported. Is there a way to fix this?
Optional chaining is a es10 feature. Why must me set the target to es5? If I set the target to es6 or above. The optional chaining is still in the compiled js.
https://github.com/angular/angular/blob/b5ab7aff433a67cddaa55e621d17b1a1b07b57c2/packages/common/src/location/location_strategy.ts#L176-L178 Why this ?. is kept even I set target to es2015? It will only be remove with es5 target.
@SetoKaiba you need to double check your settings. Compiling with target: "ES2015"
downlevels optional chaining for me. Try this simple Playground example with a target of ES2015 (under the TS Config dropdown menu). I can step all the way up to ES2019 and it still downlevels, then at ES2020 it's preserved in the emit. At any rate, this isn't Angular's fault.
@thw0rted I confirmed that it's caused by the library code is not compiled against ES2015. @alan-agius4 suggested a fix for this. Please check #22270.
Are you sure that's the right issue number? Anyway, Angular is supposed to dynamically compile against a target to match your project -- I hadn't thought about it but I guess that could be bugged / broken in some circumstances. Anyway, even if it is Angular's "fault", it doesn't belong on this issue, which is specifically about supporting optional chaining syntax in templates.
@thw0rted Sorry. It's moved to another project. This is the right issue. https://github.com/angular/angular-cli/issues/22270
@SetoKaiba, your issue in not related to this issue.
@thw0rted Sorry. This issue mentioning optional chaining operator. I thought it's related. It should be related to the issue I create,
This feature request is about optional chaining schematics in templates.
The really annoying thing about this is that it breaks the type system, because compile-time type checks think foo?.value
in a template is T | undefined
, whereas it's actually T | null
.
For example, if you have a component with an Input of a type that allows undefined
values but does not allow null
values, then using optional chaining when binding to that input may result in it being assigned the value null
, but the compiler does not complain about this, even with strictTemplates
enabled.
What's worse, if you change the input type to allow null and disallow undefined, then the compiler fails as it thinks the optional chaining syntax may return undefined, even though it actually returns null:
Type 'string | undefined' is not assignable to type 'string | null'.
Type 'undefined' is not assignable to type 'string | null'
[value]="foo?.value"
~~~~~
@daiscog that's true. it's also tracked in a separate issue: https://github.com/angular/angular/issues/37622
Another workaround is to use the "slice" or the "at" method:
array?.slice(0, 1)[0]
or
array?.at(0) // Only for es2022+
we just run into this issue and would love to see a fix for it. ❤️
🚀 feature request
Relevant Package
@angular/compiler
Description
Optional chaining[1] reached stage 4. We've been supporting similar syntax in templates for a while now, calling it the "safe navigation operator"[2]. For simplicity and smaller payload, we can consider aligning with the spec in future versions of the framework.
There are a couple of semantical and syntactical differences between optional chaining and safe navigation.
Syntax
Optional chaining has the following syntax:
Safe navigation supports only direct property access. Optional chaining supports this, as well as, method calls and function calls. Function calls are particularly useful in iterators:
Semantics
With optional chaining, the expression
a?.b
will be translated toa == null ? undefined : a.b
. In Angular, the semantics of the same expression would benull == a ? null : a.b
.If
a
isnull
orundefined
, the expressiontypeof a?.b
would evaluate to"object"
with optional chaining and"undefined"
in Angular's safe navigation operator.Except the mentioned difference above, method calls are compiled similarly:
In both, optional chaining and safe navigation in templates, stacking the operators is translated the same way:
(a?.b).c?.d
becomesnull == a ? null : null == a.b.c ? null : a.b.c.d
.Another difference seems to be the way parentheses are handled. The optional chaining spec defines that
null==e.foo?null:e.foo.b.c
should be translated to(a == null ? undefined : a.b).c
. In Angular the same expression translates tonull == a ? null : a.b.c
.PS: looks like the last issue is fixed by https://github.com/angular/angular/pull/34221.
[1] Optional chaining spec https://github.com/tc39/proposal-optional-chaining [2] Safe navigation https://angular.io/guide/template-syntax#safe-navigation-operator