In an Angular template, how is {{ discounted ? net : gross | currency }} parsed, and where does the pipe rank against ?? and ternaries?
answer
- only one branch gets formatted
- pipe is below other binary operators
- but above the conditional
- parentheses make intent explicit
basics
~20 sIt 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 sAngular'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<!-- 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
Remember that a pipe after a ternary formats only the last branch, and that parentheses fix it.
State the full ranking: pipe below +, &&, || and ??, above the ternary, and explain why the else-branch captures it.
In review, insist on parentheses next to conditionals and move non-trivial expressions into computed values so both branches are tested.
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