Skip to content

feat(compiler): add nativeOptionalChainingSemantics option#67068

Open
kbrilla wants to merge 1 commit intoangular:mainfrom
kbrilla:feat/implement-optional-chaining
Open

feat(compiler): add nativeOptionalChainingSemantics option#67068
kbrilla wants to merge 1 commit intoangular:mainfrom
kbrilla:feat/implement-optional-chaining

Conversation

@kbrilla
Copy link
Contributor

@kbrilla kbrilla commented Feb 14, 2026

This PR introduces nativeOptionalChainingSemantics, a compiler option that aligns Angular template safe navigation (?.) with native ECMAScript optional chaining behavior.

Problem

Angular's safe navigation operator (?.) has historically returned null when short-circuiting, while JavaScript/TypeScript's optional chaining returns undefined. This causes subtle bugs:

  • config?.timeout ?? 30 behaves differently in templates vs TypeScript — in templates, null passes through ?? when it shouldn't
  • config?.timeout === undefined is false in templates but true in TypeScript
  • Type inference already uses | undefined, creating a mismatch between static types and runtime behavior

Community issues: #34385, #37622, #37619

Solution

Compiler option: nativeOptionalChainingSemantics

{
  "angularCompilerOptions": {
    "nativeOptionalChainingSemantics": true
  }
}

When enabled, a?.b evaluates to undefined (instead of null) at runtime when a is nullish, matching JavaScript/TypeScript semantics.

Per-component override: optionalChainingSemantics

Components and directives can override the project-wide setting:

@Component({
  optionalChainingSemantics: 'legacy', // or 'native'
  template: `{{ config?.timeout }}`,
})
export class MyComponent {}

This enables gradual migration — individual components can opt in/out independently, and the setting is preserved in partial declarations for library distribution.

Host bindings & directives

Each directive/component uses its own semantics independently, including:

  • Template expressions
  • Host binding expressions ([attr.x], [class.x], [style.x])
  • hostDirectives — each keeps its own semantics regardless of the parent component

Extended template diagnostic: NG8119

When nativeOptionalChainingSemantics is enabled project-wide, components that haven't adopted native semantics (or use explicit 'legacy') receive an informational diagnostic (NG8119) flagging ?. usage that returns null instead of undefined.

Migration schematic

ng generate @angular/core:optional-chaining-semantics-migration

Uses AST-based analysis to identify ?. usage in templates. The migration adds ?? null coalescing where needed for components that depend on the null return value, making them safe to enable native semantics.

Design note: The migration does NOT blindly add ?? null everywhere — it analyzes usage patterns (strict equality checks, nullish coalescing, ternary fallbacks) and only patches expressions where the null-to-undefined change would alter observable behavior.

Partial linker support

The optionalChainingSemantics metadata is written into partial declarations (ɵɵngDeclareComponent/ɵɵngDeclareDirective) so the linker respects the intended behavior when consuming pre-compiled libraries.

Documentation

Updated adev/src/content/tools/cli/template-typecheck.md with a new "Optional chaining semantics" section covering the option, migration, and caveats.

Implementation details

  • Compiler pipeline: expand_safe_reads phase checks the compilation unit's optionalChainingSemantics and emits either null or undefined for the safe ternary fallback
  • JIT support: nativeOptionalChainingSemantics is available via setJitOptions() for JIT-compiled components
  • No runtime overhead: The change only affects what constant value the compiler emits in the ternary expansion — no new runtime code paths

What this does NOT change

  • Libraries published to npm retain whatever semantics they were compiled with
  • Existing code without the flag continues to work identically (legacy null behavior)
  • The flag is opt-in and off by default

Live Demo

A complete demo application showcasing the feature is available at:
https://kbrilla.github.io/angular-next-features/

It demonstrates:

  • Side-by-side legacy vs native behavior
  • Mix-and-match components with different semantics
  • Host binding behavior
  • Migration patterns and edge cases

AI Disclosure: This feature was implemented with the assistance of GitHub Copilot (Claude) under my orchestration and direction. All architectural decisions, code review, and testing were performed by me. The AI assisted with code generation, research, and iterative refinement.

@google-cla
Copy link

google-cla bot commented Feb 14, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@angular-robot angular-robot bot added detected: feature PR contains a feature commit area: compiler Issues related to `ngc`, Angular's template compiler area: core Issues related to the framework runtime labels Feb 14, 2026
@ngbot ngbot bot added this to the Backlog milestone Feb 14, 2026
@kbrilla kbrilla marked this pull request as ready for review February 14, 2026 11:52
@pullapprove pullapprove bot requested a review from mmalerba February 14, 2026 11:52
@JeanMeche
Copy link
Member

We usally try distance us from introducing new properties to the existing decorators. Also we prefer to not have local behavior like this.

Let me bring the suggestion to the team.

@kbrilla
Copy link
Contributor Author

kbrilla commented Feb 14, 2026

We usally try distance us from introducing new properties to the existing decorators. Also we prefer to not have local behavior like this.

Let me bring the suggestion to the team.

Thanks for having a look at it! I went with this approach as every other I could think of would be compleatly breaking existing code or introducing really 'not pretty' migration. This approach is similar to Default/OnPush/Standalone:true/false and introduce similar granual adaptation as standalone allowed. Still, I'm happy it did not got denied straight away :)

@kbrilla
Copy link
Contributor Author

kbrilla commented Feb 14, 2026

I also forgot to mention that with inlay hints from my other pr it works be more visible what is actuall behaviour without looking at the component directive flag

Add a compiler option that aligns Angular template safe navigation (?.)
with native ECMAScript optional chaining behavior. When enabled, a?.b
evaluates to undefined (instead of null) at runtime when a is nullish.

Includes:
- nativeOptionalChainingSemantics tsconfig option (project-wide)
- Per-component optionalChainingSemantics metadata override
- Host binding and hostDirective support (each uses own semantics)
- Extended template diagnostic NG8119 for legacy usage detection
- Migration schematic: ng generate @angular/core:optional-chaining-semantics-migration
- Partial linker support for library distribution
- JIT support via setJitOptions()
- Documentation in adev/src/content/tools/cli/template-typecheck.md

Fixes angular#34385, angular#37622, angular#37619
@kbrilla kbrilla force-pushed the feat/implement-optional-chaining branch from c99cfe4 to dfae32f Compare February 14, 2026 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: discuss area: compiler Issues related to `ngc`, Angular's template compiler area: core Issues related to the framework runtime detected: feature PR contains a feature commit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants