Empower growth and innovation with the latest Front-end Dev insights

OnPush child component not refreshing with polling data in Angular: replace the array reference first, or add markForCheck first?

Oct 12, 2026 Read: 15

When an OnPush child component doesn't refresh with new polling data in Angular, most of the time change detection isn't broken — the change simply didn't land in a channel OnPush recognizes. OnPush's semantics tighten “check every round” into “check only on an explicit signal”: template events, an @Input reference change, a new value from an async pipe or signal, or a manual markForCheck are what wake up that round. Based on 2026 delivery experience, first confirm whether the external callback converges and whether the array reference is replaced, then decide whether a marker is needed; in most cases you only need a new reference, and you don't have to fall back to Default.

1. OnPush changes the trigger timing, it doesn't turn detection off

By default Angular walks change detection down from the root component, and the cost grows when there are many components and heavy template expressions. OnPush adds an admission gate to a component: only when Angular believes the component may have changed does it check it and its subtree. It tightens “check every round” into “check only on a signal” — it doesn't skip refreshing.

  • Event bindings in the template (click, input, submit, and so on) firing inside this component;
  • The reference of a value passed in through @Input changing — a new object or array, not just a mutated property;
  • An Observable subscribed through an async pipe in the template emitting a new value;
  • When signals are used, a signal value read by the template changing;
  • An explicit markForCheck() call in code, or detectChanges() to immediately check the current view and its subtree.

When “not refreshing” shows up, go through these one by one and see which category this change falls into; if it falls outside, you have to add a channel Angular can perceive.

2. When data doesn't refresh, check with four questions

Ask these four in order, so you don't reach for markForCheck right away. Manual markers scattered through business code make it hard later to tell which refresh was supposed to happen and which was forced, and the debugging cost shifts to whoever takes over.

  1. Where is the trigger source: did a template event cause this change, or did it come from a subscription callback, a timer, a third-party SDK callback? Angular doesn't necessarily perceive the latter, and the common practice is to converge them into one entry point and mark there.
  2. Did the reference change: is the array being pushed to, the object having a property mutated, or the whole thing replaced? OnPush compares references, and a changed value and a changed reference are two different things.
  3. Who gets the marker: markForCheck marks ancestors along the path dirty and waits for the next round; detectChanges immediately checks the current view and its child views, and it won't go back and recompute parent template expressions — use it wrong and you either miss a refresh or add cost.
  4. Does this component really need OnPush: if its internal state changes on its own frequently and it isn't on a performance bottleneck, Default is actually less trouble; don't push a performance strategy as if it were a code standard.

Walk these four first, then decide whether to replace the reference, add a marker, or stay on Default — that's usually steadier than adding a marker straight away.

3. In delivery: the constraints, the approach, and the cost of a polling list

The more common real-world case: a back-office list page, two people, a two-week schedule, data from three APIs plus one polling loop, and it has to be embedded in a WebView. The approach was to switch the row components to OnPush, always replace the list input with a new array and add trackBy, call markForCheck once at the entry point for the external polling callback, and write “who owns the marker” into the acceptance checklist. The result was fewer checks while scrolling and steadier interaction; but the first version missed marking the push branch, and production saw an intermittent “new data doesn't refresh” issue — rework and regression came to roughly half a day to a day. The typical range for this kind of cost is half a day to a day, depending on whether regression coverage includes external callbacks and error states; the root cause is usually not that OnPush is hard, but that marker ownership wasn't spelled out.

4. Default, OnPush, and signal inputs compared: typical ranges

These three aren't a new-replaces-old relationship; they just fit different surfaces. Below we break them out by trigger method, performance behavior, onboarding and debugging cost, migration timeline, and who they fit — timelines and magnitudes are experience ranges, and the actual figures depend on page size and team familiarity.

  • Trigger method: Default checks every round; OnPush relies on template events, new references, async pipe, signals, or manual markers; signal inputs turn the value change itself into a trackable template dependency.
  • Performance behavior: on large lists and pure presentational components, the drop in check count with OnPush is more obvious; on simple form pages the gain is limited, and the typical range is only a small fluctuation.
  • Onboarding and debugging cost: OnPush requires the team to understand references and markers, and the familiarization period is commonly one to three days per person; signals are more intuitive to write, but migrating old code has to be done in batches, and the mixed period puts more weight on conventions.
  • Migration timeline: for small to mid-size pages, a single-component change including regression is commonly half a day to two days; for list-with-polling pages it's commonly two to five days.
  • Who it fits: presentational, list, and input-driven components suit OnPush; prototype pages with lots of internal mutable state and requirements that change daily are better left on Default.

The point of the comparison isn't to pick the more advanced style, it's to confirm which channel this missed refresh belongs to.

5. Where it applies and where it doesn't

OnPush isn't a switch that's better the earlier you add it. It suits scenarios where the performance bottleneck has already appeared and the component's input and output boundaries are clear; for early pages whose requirements are still changing fast, sorting out component boundaries and data flow pays off more than adding a strategy first.

  • Fits: pure presentational row and card components; child components split out of large lists and input-heavy parents; pages where you've already located a template expression that's too heavy and detection is costly.
  • Doesn't fit: pages where the component frequently changes its own state through subscriptions or timers; short-term projects where nobody on the team wants to maintain marker rules; a presentational site with only one or two pages and no performance pressure yet.

You can boil the test down to one line: if a component's data mainly comes from inputs and template events, OnPush fits; if a lot of its refreshing comes from internal imperative assignment, subscriptions, and timers, converge those channels first, then talk about strategy. Skip that step and add OnPush directly, and the problem often just shifts from “janky” to “sometimes doesn't refresh”.

Common questions

After this.list.push() in an OnPush component the page doesn't refresh — is adding markForCheck enough?

Not necessarily. The marker usually rescues this one round, but the input is still the same mutated reference, and the next time the parent passes the same array it will miss the refresh again; the common fix is to assign a new array instead.

Can an async pipe trigger a check automatically inside an OnPush component?

Yes. When the async pipe receives a new value it marks that view as needing a check, so putting the subscription in the template is usually less work than subscribing manually in the component and assigning — and it leaves one less unsubscribe burden.

The parent is also OnPush, and the child still doesn't update after getting a new object — what do I check first?

First check whether that new object in the parent was produced in a channel Angular can perceive, such as a manual subscription, a timer, or a third-party callback; if the parent isn't marked, the child never gets to this round of checking.

When switching a component from Default to OnPush, how much regression time should I set aside?

As an experience range, half a day to a day for small to mid-size pages, one to two days for list-with-polling pages; focus regression on external callbacks, returning after route reuse, and error states — not just the happy path.


If you're actually going to make the change, start with one pure presentational row component: switch inputs to immutable updates, add trackBy, converge external callbacks into a single marking entry point, run one round of regression, then decide how wide to roll it out. If a page's data largely comes from imperative assignment inside the component and nobody on the team maintains marker rules, staying on Default and splitting the component smaller is usually steadier than forcing OnPush.

Have a similar project in mind?
Contact us for a one-to-one project reference proposal
Obtain Proposal
Interested in this topic?
10-year tech team — reference proposal within 24 hours
Obtain Proposal
Are you ready?
Then reach out to us!
+86-13370032918
Discover more services, feel free to contact us anytime.
Please fill in your requirements
What services would you like us to provide for you?
Your Budget
ct.
Our WeChat
Professional technical solutions
Phone
+86-13370032918 (Manager Jin)
The phone is busy or unavailable; feel free to add me on WeChat.
E-mail
349077570@qq.com
Submitted successfully
Thank you for your trust. We will contact you soon!
Recommended projects for you