Skip to content

Align with the optional chaining spec #34385

Closed

Description

@mgechev

🚀 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:

obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call

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:

iterator.return?.()

Semantics

With optional chaining, the expression a?.b will be translated to a == null ? undefined : a.b. In Angular, the semantics of the same expression would be null == a ? null : a.b.

If a is null or undefined, the expression typeof 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:

a?.b()
a == null ? undefined : a.b()

In both, optional chaining and safe navigation in templates, stacking the operators is translated the same way: (a?.b).c?.d becomes null == 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 to null == a ? null : a.b.c.

PS: looks like the last issue is fixed by #34221.


[1] Optional chaining spec https://github.com/tc39/proposal-optional-chaining
[2] Safe navigation https://angular.io/guide/template-syntax#safe-navigation-operator

Activity

  1. added
    area: compilerIssues related to `ngc`, Angular's template compiler
    featureLabel used to distinguish feature request from other issues
    on Dec 12, 2019
  2. modified the milestone: Backlog on Dec 12, 2019
  3. thw0rted commented on Feb 24, 2020

    @thw0rted

    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.

  4. rodolfojnn commented on Apr 2, 2020

    @rodolfojnn

    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

  5. craftmaster2190 commented on May 8, 2020

    @craftmaster2190

    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

  6. craftmaster2190 commented on May 8, 2020

    @craftmaster2190

    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.

  7. johnwest80 commented on Jun 1, 2020

    @johnwest80

    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]?

  8. ajafff commented on Jun 17, 2020

    @ajafff

    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
  9. Sampath-Lokuge commented on Sep 28, 2020

    @Sampath-Lokuge

    Hi,

    For me it shows this:

    Parser Error: Unexpected token [, expected identifier or keyword at column 33 in [
    
                        {{userItemModel?.item?.priceList?.[0].sellerUrl}}
                    ] in 
    

    SOF Link: https://stackoverflow.com/questions/64104994/optional-chaining-is-not-working-cannot-read-property-0-of-undefined

  10. adeel55 commented on Dec 26, 2020

    @adeel55

    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

  11. snebjorn commented on Jun 2, 2021

    @snebjorn

    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.

  12. 36 remaining items

  13. added 12 commits that reference this issue on Apr 10, 2026
    4b3b6b2
    fde65e3
    cc25479
    eabdf36
    c9656ef
    f9e97c4
    a5367d3
    b2de89a
    778f184
    61636b3
    b7fea02
    d6bdb74
  14. angular-automatic-lock-bot commented on May 29, 2026

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  15. locked and limited conversation to collaborators on May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: compilerIssues related to `ngc`, Angular's template compilerarea: coreIssues related to the framework runtimecore: binding & interpolationIssue related to property/attribute binding or text interpolationfeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions