skip to content

In an Angular template, how is {{ discounted ? net : gross | currency }} parsed, and where does the pipe rank against ?? and ternaries?

level: middleimportance: should knowfreq 42%

answer

  1. only one branch gets formatted
  2. pipe is below other binary operators
  3. but above the conditional
  4. parentheses make intent explicit

basics

~20 s

It parses as discounted ? net : (gross | currency), so only gross is formatted. The pipe binds more loosely than +, &&, || and ?? but more tightly than the ternary, so wrap the conditional in parentheses.

solid answer

~30 s

Angular's pipe operator has **lower precedence than every other binary operator**, including `+`, `&&`, `||` and `??`, so `a ?? b | currency` formats whichever value wins. But it has **higher precedence than the ternary**: each ternary branch is parsed as its own pipe expression, so a trailing `| currency` attaches to the else-branch only. In `discounted ? net : gross | currency`, a discounted invoice shows a raw number. The fix is `(discounted ? net : gross) | currency`. The Angular guide recommends parentheses whenever precedence may be ambiguous, and pipes are not allowed in event bindings at all.

code

html · 8 lines
html
<!-- Bug: parsed as discounted ? net : (gross | currency) -->
<p>{{ discounted ? net : gross | currency }}</p>

<!-- Fix: format whichever branch is chosen -->
<p>{{ (discounted ? net : gross) | currency }}</p>

<!-- Fine: ?? binds tighter than the pipe -->
<p>{{ override ?? total | currency }}</p>

go deeper

for a junior

Remember that a pipe after a ternary formats only the last branch, and that parentheses fix it.

for a middle

State the full ranking: pipe below +, &&, || and ??, above the ternary, and explain why the else-branch captures it.

for a senior

In review, insist on parentheses next to conditionals and move non-trivial expressions into computed values so both branches are tested.

for a principal

Set template conventions and lint rules for a team so precedence bugs are prevented rather than debugged per screen.

## The rule in one line In an Angular template expression the pipe operator `|` binds **more loosely than every other binary operator** (`+`, `-`, `*`, `/`, `%`, `&&`, `||`, `??`) but **more tightly than the conditional (ternary) operator** `? :`. When the two meet without parentheses, the pipe attaches to the **last branch** of the ternary. ## Worked examples Take an invoice template: ```html {{ discounted ? netTotal : grossTotal | currency }} ``` Angular parses this as: ```html {{ discounted ? netTotal : (grossTotal | currency) }} ``` So when `discounted` is `true` the page shows a bare number such as `1234.5`, and only the other branch is formatted. The fix is explicit parentheses: ```html {{ (discounted ? netTotal : grossTotal) | currency }} ``` Now compare operators that bind **more tightly** than the pipe: | Template | Parsed as | Result | |---|---|---| | `subtotal + shipping \| currency` | `(subtotal + shipping) \| currency` | the sum is formatted | | `override ?? total \| currency` | `(override ?? total) \| currency` | whichever value wins is formatted | | `paid && total \| currency` | `(paid && total) \| currency` | the `&&` result is formatted | | `a ? b : c \| currency` | `a ? b : (c \| currency)` | only `c` is formatted | Arithmetic, `&&`, `||` and `??` are evaluated first and the pipe formats their result. Only the ternary surprises people, because it is the one operator the pipe outranks. ## Why the grammar works this way Angular's expression parser reads a pipe expression as "an expression, then optionally `|` and a pipe name". The ternary is the outermost level of an expression, and each of its branches is itself parsed as a pipe expression. So a `|` written after the colon is swallowed by the else-branch. This is also why a pipe can legitimately appear inside a branch: `{{ ok ? (total | currency) : 'n/a' }}`. ## Pipe arguments are expressions too Everything after a colon in a pipe is a separate argument expression: ```html {{ total | currency: invoice.currency : 'symbol' : '1.2-2' }} ``` When an argument is itself conditional, wrap it in parentheses so the reader does not have to know the grammar: ```html {{ total | currency: (showCode ? 'code' : 'symbol') }} ``` ## Where the pipe operator is not allowed - **Event bindings.** An expression like `(click)="save(total | currency)"` fails to compile with the message "Cannot have a pipe in an action expression". Format in the component, or bind the formatted value elsewhere. - **Bitwise OR.** Because `|` means pipe, Angular template expressions have no bitwise OR at all. ## Guidance for code review 1. Parenthesise any pipe that sits next to a ternary, even when the unparenthesised version happens to work; the Angular guide itself recommends parentheses whenever precedence may be ambiguous. 2. Prefer computing complex values in the component, for example with a `computed()` signal, and keep the template expression a single value plus its pipe. 3. Test both branches of a conditional that feeds a pipe; the unformatted branch is the one that ships unnoticed. ## Why it matters more than it looks Precedence bugs next to a pipe are quiet. Nothing throws, the template compiles, and the unformatted branch still shows a plausible number, so the defect usually surfaces as a screenshot from a customer: one invoice reads `$1,234.50`, the next reads `1234.5`. Unit tests that only render the default branch never see it. That is why interviewers like this question: it checks whether a candidate knows the grammar rather than relying on the happy path. ## A quick self-test - `{{ price * qty | number: '1.2-2' }}` formats the product, because `*` binds tighter. - `{{ vip ? 'Gold' : tier | titlecase }}` title-cases only `tier`. - `{{ (vip ? 'Gold' : tier) | titlecase }}` title-cases whichever string is chosen.

  • Can you use a pipe inside an Angular event binding such as (click)="save(total | currency)"?
    No. The template compiler rejects it with "Cannot have a pipe in an action expression". Format the value in the component, for example with a `computed()` or by calling a formatting function, and pass that.
  • Why does Angular's template syntax have no bitwise OR?
    Because `|` is taken by the pipe operator. Template expressions do not support bitwise operators, so any bit manipulation belongs in component code.

saying these in an interview costs you the question

  • Thinks the pipe applies to the whole ternary expression
  • Believes ?? binds more loosely than the pipe operator
  • Adds parentheses around a + b before piping, thinking they are required
  • Uses a pipe inside a (click) handler expression
  • Assumes | in a template can mean bitwise OR