skip to content

In an Angular template, what is the difference between user?.name and user!.name, and what does each do when user is null?

level: middleimportance: should knowfreq 48%

answer

  1. one is a runtime guard
  2. one exists only for the type checker
  3. short-circuit versus TypeError
  4. undefined since v22
  5. narrowing beats asserting

basics

~20 s

user?.name is a runtime guard: if user is null or undefined it short-circuits and yields undefined (null before v22). user!.name is a TypeScript non-null assertion that only silences the type checker; at runtime a null user still throws a TypeError.

solid answer

~40 s

The **safe-navigation operator** `?.` generates a runtime check: when the left side is `null` or `undefined` the rest of the chain is skipped and the expression yields `undefined` since Angular 22 (it yielded `null` before). The **non-null assertion** `!` is TypeScript syntax that Angular templates accept for the template type checker: it tells strict templates "this is not null here" and is erased from the compiled code, so `user!.name` with a `null` user throws `TypeError` during change detection. Use `?.` (often with `??` for a fallback) when the value can really be missing, and `!` only when you know it cannot be but the checker cannot see why. Better still, narrow with `@if (user(); as u)` so neither is needed.

code

ts · 17 lines
ts
import { Component, signal } from '@angular/core';

interface User { name: string }

@Component({
  selector: 'app-welcome',
  template: `
    <p>{{ user()?.name ?? 'guest' }}</p>
    <!-- <p>{{ user()!.name }}</p>  throws while user() is null -->
    @if (user(); as u) {
      <p>Signed in as {{ u.name }}</p>
    }
  `,
})
export class Welcome {
  protected readonly user = signal<User | null>(null);
}

go deeper

for a junior

Know that ?. guards against a missing value at runtime while ! only silences the type checker.

for a middle

Explain the v22 undefined result of ?., why ! is erased, and how @if with as narrows instead.

for a senior

Review templates for ! used on values that are null during the first render, and prefer narrowing or fallbacks with ??.

for a principal

Set a lint and review stance on non-null assertions in templates so type safety is not traded for convenience across the codebase.

## Two operators that look alike Both operators sit between an object and a property, and both appear when a value "might be null". They do completely different jobs. | | `user?.name` | `user!.name` | |---|---|---| | Name | safe navigation (optional chaining) | non-null assertion | | Exists at runtime | yes - compiled into a check | no - erased by the compiler | | When `user` is `null` | yields `undefined` (v22+) | throws `TypeError` | | Effect on type checking | result type includes `undefined` | removes `null`/`undefined` from the type | | Typical use | value genuinely optional | value guaranteed but checker cannot prove it | ## The safe-navigation operator `?.` guards a read at **runtime**. If the left side is `null` or `undefined`, evaluation stops and the whole chain produces a nullish result instead of throwing. - **Since Angular 22** that result is `undefined`, the same as JavaScript's optional chaining. **Before v22** Angular returned `null`. Code that compared the result with `=== null` or passed it to a function checking for `null` behaves differently after the upgrade; the `ng update` migration wrapped such uses in `$safeNavigationMigration(...)` to keep the old result. - In **interpolation** the difference is invisible, because `null` and `undefined` both render as an empty string. - It combines naturally with `??`: `{{ user()?.name ?? 'guest' }}` shows a fallback for any missing link. - With strict templates, applying `?.` to a value whose type cannot be `null` or `undefined` triggers the extended diagnostic "Optional chain not nullable", which is a hint that the guard is dead code. ## The non-null assertion `!` comes from **TypeScript**. Angular's template parser accepts it so that templates can talk to the **template type checker** in the same way class code talks to `tsc`: - It narrows the type: `user!.name` is typed as if `user` could not be `null`. - It is **erased** from the generated code; nothing checks the value at runtime. - If the assumption is wrong, the read throws a `TypeError` ("Cannot read properties of null") while Angular checks the view, and the error goes to Angular's error handling instead of the value rendering. A related tool is `$any(expr)`, which casts to `any` for the checker and is likewise erased at runtime. ## How the type checker sees each With strict templates, Angular type-checks every expression against the component's types, so both operators change what the checker infers: 1. `user()?.name` on a `User | null` signal is typed `string | undefined`, so passing it to an input typed `string` is an error until you add a fallback (`?? ''`). 2. `user()!.name` is typed `string`, so the same input binding compiles - which is exactly why the assertion is tempting and why it hides a real `null` state. 3. `$any(user()).name` is typed `any`, turning checking off for that expression entirely. ## When to use which 1. **Value can really be missing** (a signed-out user, data still loading): use `?.`, usually with `??` for display text. 2. **Value is guaranteed** by something the checker cannot see (a route resolver ensured it, a parent only renders the child when set): `!` is acceptable, but document why. 3. **A block of markup depends on the value**: prefer a control-flow block such as `@if (user(); as u) { ... }`, which narrows the type for everything inside and avoids repeating either operator. ## Common mistakes - Using `!` "to make the red squiggle go away" on a value that is `null` during the first render - for example a signal that starts as `null` until an HTTP response arrives. The template then throws on first check. - Believing `!` provides a default value or skips rendering. - Chaining `?.` everywhere on values that can never be nullish, which hides real type errors and makes readers think the data is optional. - Expecting `user?.name` to still be `null` in v22 code that compares with `=== null`.

  • Why can user()!.name pass the build yet crash on the first render?
    The `!` assertion only removes `null` from the type seen by the template type checker, so the build succeeds. It is erased from the compiled template, so if the signal still holds `null` when the view is first checked, reading `.name` throws a `TypeError`.
  • When is the optional chain not nullable diagnostic useful?
    It warns when `?.` is applied to a value whose declared type can never be `null` or `undefined`. That usually means the guard is unnecessary, or that the type is wrong and should include `null`; either way the template is misleading readers about what can be missing.

?. is checking that a mailbox exists before opening it; ! is telling the building inspector not to worry about the mailbox - the inspector moves on, but if there is no mailbox your hand still meets the wall.

saying these in an interview costs you the question

  • user!.name returns undefined when user is null
  • The ! operator provides a runtime null check in templates
  • user?.name still yields null in Angular 22
  • ?. and ! compile to the same code
  • Adding ! is the right fix for any possibly-null template error