| title | Pipes |
|---|---|
| description | Custom, built-in and library pipes, where they are used, live instances, call recording and a pipe lint. |
The Pipes tab lists your @Pipe classes, the built-in pipes from @angular/common that your templates use, and pipes from libraries that the page uses. It shows the live instances on the page, and records calls when you ask it to.
Search by pipe name, class or file. Narrow the list with Show pipes: All pipes, Custom, Built-in, Runtime only, Impure, On the page or Called while recording. Order it with Sort pipes: Source order, Most calls or Most time (time spent in transform). The two sorts put the pipes with recorded calls first, hottest at the top, and keep the others in source order below.
Each row shows the pipe name, its class, and chips:
- N live: instances on the page.
- built-in, and NgModule for pipes that are not standalone.
- runtime only for a pipe the source scan did not find, such as
translateorngrxPushfrom a library. Its row says No source location. - pure or impure.
- stale? when recording caught a possible stale value.
- churning when an instance returned an equal result 20 calls in a row while recording.
- N calls and the total time in
transform, while recording.
The row also counts usages: +N more for a built-in pipe, and N uses for your own pipes.
Hover or focus a row to highlight the first component that uses it.
A runtime-only pipe is listed while the page uses it, and leaves the list when the page stops using it. If the source scan fails while the page reports pipes, the list keeps the runtime-only pipes and says Couldn't scan the source with a Try again button. If an earlier scan worked, the list also keeps the pipes that scan found and says so.
Select a pipe to see its class, whether it comes from @angular/common or your project or was only seen on the page, its file, and whether it is standalone and pure. A runtime-only pipe has no file, and the page does not say whether it is standalone, so those rows are left out. A pure pipe reruns only when an argument changes. An impure pipe reruns on every check.
Every template that uses the pipe, with file and line, for your own pipes and the built-in ones. The scan reads interpolations, bound attributes, @if, @else if, @for, @switch and @case conditions, @defer triggers and @let values.
The number of instances and the components that use them. Click, hover or focus a component chip to highlight it. With recording on, this block adds the call count, the time spent in transform (total, average and slowest call), the errors it threw with the last message, the last input and output, and the last caller. When several instances were called, Per instance lists each one with its component, values, call count, time and caller. Click a component there to highlight it. The list is capped and says how many instances there are when it has more. A churning pipe gets a note above the block that says what to change.
When several tabs report pipes, the panel of a tab shows that tab's pipes and | async rows only. Without a tab of its own, the panel shows every tab together.
While recording, the page compares each call's result with the one before. A pipe is marked churning once an instance returned an equal result 20 calls in a row:
| Pipe | Counts as an equal call |
|---|---|
| Pure | Any equal result. The pipe reran because its input changed reference, but the contents and so the output stayed the same. |
| Impure | An equal object or array returned as a new reference, which makes the bindings that read it update for nothing. |
An impure pipe that returns the same reference or primitive on every check is how impure pipes run, so it is never marked. Results are compared three levels deep and up to 50 items or keys per level. A bigger result never counts as equal. A different result or an error starts the count again, and the mark stays until the next recording.
When templates use | async, the tab lists each subscription with its component and latest value. This needs no recording. Each | async subscribes on its own. Two on the same cold source run the work twice, for example two HTTP requests, so the tab marks those rows possible duplicate subscription. Sources whose subscribers share one execution are not marked: Promises, Subjects (BehaviorSubject, ReplaySubject and the rest), an asObservable() or toObservable() view of one, and NgRx store selects. The page can't see share() or shareReplay() in a pipe, so a source shared that way is still marked, and you can leave it as it is.
The list keeps 200 rows per tab. When a page has more, the section says how many it shows out of the total.
A source that changes on three reports in a row, like getData() | async returning a new Observable on each check, is marked resubscribing. AsyncPipe unsubscribes and subscribes again every time the source changes, so with HttpClient each check sends a request. A method that returns the same Observable each time is not marked.
| Rule | Severity | Finds |
|---|---|---|
impure-pipe-in-for |
warning | An impure pipe inside an @for block or an *ngFor element. It runs on every check, possibly once per row. async is skipped because it is cheap there, and json has its own rule. |
json-pipe-in-template |
info | | json left in a template. It is a debugging aid. |
signal-read-in-pure-pipe |
warning, info | A pure pipe whose transform() reads a signal, input, computed, linkedSignal, toSignal or model field of the pipe (warning). A call with no arguments on an injected service, like this.rates.current(), might be a signal read (info). |
async-on-call |
info | A method call piped to | async, like getUsers() | async. If the method builds a new Observable each time, AsyncPipe resubscribes on every check. Signal, input, computed and toSignal fields of the component are skipped. |
If the pipe in a finding is in the pipe list, its name is a button. Click it to select that pipe and open its details. A search or Show pipes filter that hides the pipe is cleared first; filters that already show it stay. A pipe that is not in the list, like a library pipe the page does not use, stays plain text.
@Pipe classes with name, class, file, standalone and pure flags. Built-in pipes with every usage site.
The overlay finds pipe instances in the rendered views, library pipes included. It is read-only until you record.
The server checks your source for the four rules above.
The built-in list covers the @angular/common pipes: async, currency, date, number, i18nPlural, i18nSelect, json, keyvalue, lowercase, percent, slice, titlecase and uppercase. Pipes from other packages are not listed as built-in. They show as runtime only while the page uses them.
Live discovery walks the rendered views with ng.getComponent and related debug helpers. It needs a development build.
Click Record calls to count and time calls, count the calls that throw, and keep the last input and output of each pipe. A transform that throws still throws in your app. Recording patches each pipe's transform in one tab: the page the panel belongs to, or the page that reported last when the panel runs on its own. Other open tabs stay unpatched. It covers the pipes found on the page, and every pipe class the rendered templates can use, so a pipe in an @if branch that renders later is counted from its first call. A pipe class that only a newly added view brings, such as one in a lazy route you open, is patched as soon as the page sees the view, so only the calls made while that view first renders can be missed. Stop recording stops every tab that records. Click Stop recording when you are done.
Each recording starts from zero, so a second recording measures one interaction on its own. The button switches to Stop recording once a page confirms that it is recording. If no page is connected, the tab says so and stays on Record calls.
Pick Impure in Show pipes. Click Record calls, then use the page for a moment. A pipe with a high call count reruns on every check. A high total or slowest time points at a costlytransform. Make it pure, or move the work into a computed().
Click Stop recording.
Recording turns on the stale check.
It marks a pure pipe that got an argument whose contents changed while its reference stayed the same, and did not rerun. Arrays, objects, Date, Map and Set values are compared. The chip clears once the pipe reruns.
Replace the object or array instead of mutating it, so the pipe reruns.
Pick Called while recording in Show pipes, and look for churning rows. For a pure pipe, find the input that is rebuilt on every check (an array or object literal, or a method call in the template) and keep it in a field or a computed(). For an impure pipe, move the logic to a computed(), or return the previous result when the new one is equal. suggest-pipe-migrations lists the template uses to change.
Open Async subscriptions and look for possible duplicate subscription rows. If the source is not already shared with share() or shareReplay(), subscribe once with @let, or turn the observable into a signal with toSignal().
Look for resubscribing rows in Async subscriptions, and for async-on-call in Lint. Keep the Observable in a field, or read it with toSignal() or httpResource().
| Tool | Live | What it does |
|---|---|---|
pangular:scan-pipes |
no | Custom pipes and the built-in pipes in use. |
pangular:scan-pipe-usage |
no | Runs the lint rules above. |
pangular:explain-pipe |
partly | One pipe by name: where it is declared or used, purity, live counts, time in transform, errors, last input and output, the stale and churn warnings, resubscribing | async usages and the lint findings. |
pangular:record-pipes |
yes | Starts or stops call recording with action, like Record calls. |
pangular:suggest-pipe-migrations |
partly | Read-only suggestions for moving pipes to signals (| async to toSignal(), impure or signal-reading pipes to computed(), churning and stale pipes), with the file and line of each use. It changes no file. |
explain-pipe, record-pipes and suggest-pipe-migrations take page to pick a tab, and default to the most recent one, except that record-pipes with action: "stop" and no page stops recording on every tab. To give explain-pipe call data, call record-pipes with action: "start" or click Record calls in the panel, then use the app. See Tools. The pangular:pipe-usages resource serves the source scan and the live data as JSON.
json pipe, is parsed and redacted too, and shown without its line breaks. See Access and redaction.
It runs only while recording. It reads the template source, so it needs an unminified development build. When it can't read the template, the pipe's details say the check is off, and so does explain-pipe. It compares the first two levels of an argument and up to 50 items per level.
signal-read-in-pure-pipe can't tell a signal on an injected service from a method with no arguments, so it reports those calls at info level.
A component chip keeps up to 10 host elements per page to highlight. When a component has more, pangular:pipe-usage gives the number of distinct host elements in targetTotal. A report that combines pages keeps at most 10 host elements per component across all pages, and its targetTotal adds up the host elements of every page.
Recording is off by default. Reload the page and it is off again.
Recording is off. Callpangular:record-pipes with action: "start", or click Record calls in the panel, then use the app.
The built-in list covers the @angular/common pipes only.
The components that use each pipe, and the pipes each component's template uses.
Signals a pure pipe should not read.
Every tool a coding agent can call.
What the devtools redact, and what they don't.