Angular Change Detection Fixes Guide | Generated by AI

Home 2026.03

Question: In Angular (modern versions with stricter change detection), a worklist component updates data in a pagination child component, but the pagination UI (e.g., totalItems) only renders/updates after the user physically clicks the pagination component. How do you fix this timing/change detection issue?

Answer:

Root Cause

This is a classic change detection timing problem. When the parent (worklist) sets data on the pagination child:


Fix Strategy: Choose the Right Approach

This is the modern, idiomatic fix. Signals are reactive by nature and bypass the lifecycle timing problem entirely.

In the worklist (parent):

import { signal, computed } from '@angular/core';

export class WorklistComponent {
  totalItems = signal(0);
  items = signal<MyItem[]>([]);

  loadData() {
    this.myService.getItems().subscribe(result => {
      this.items.set(result.data);
      this.totalItems.set(result.total);  // signal update is immediate + reactive
    });
  }
}

In the pagination child:

import { input } from '@angular/core'; // Angular 17+ input signal

export class PaginationComponent {
  totalItems = input.required<number>();  // reactive signal input
}

Template (parent):

<app-pagination [totalItems]="totalItems()" />

With OnPush strategy, Angular only triggers change detection if a new reference is passed as an @Input() value — but with Signals, updates are tracked reactively and schedule re-renders automatically without waiting for the next CD pass.


✅ Option 2 — Use ChangeDetectorRef.detectChanges() in the Child

If you cannot refactor to Signals, inject ChangeDetectorRef into the pagination component and call detectChanges() when the input is received.

In pagination component:

import { Component, Input, OnChanges, SimpleChanges, ChangeDetectorRef } from '@angular/core';

export class PaginationComponent implements OnChanges {
  @Input() totalItems = 0;
  
  constructor(private cdr: ChangeDetectorRef) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes['totalItems']) {
      this.cdr.detectChanges(); // force this component + children to re-render now
    }
  }
}

ChangeDetectorRef.detectChanges() forces change detection on the current view and its children, which solves the issue when the parent’s async operation completes but the child view hasn’t been updated yet.


✅ Option 3 — Use markForCheck() in the Parent After Setting Data

If the worklist owns the change, mark the view after updating:

import { ChangeDetectorRef } from '@angular/core';

export class WorklistComponent {
  @ViewChild(PaginationComponent) pagination!: PaginationComponent;

  constructor(private cdr: ChangeDetectorRef) {}

  loadData() {
    this.myService.getItems().subscribe(result => {
      this.totalItems = result.total;
      this.cdr.markForCheck(); // schedules the OnPush tree to be checked
    });
  }
}

markForCheck() does not trigger change detection immediately, but marks all OnPush ancestors as needing to be checked — either in the current or next change detection cycle.


✅ Option 4 — Shared Reactive Service with BehaviorSubject / Observable

Use a shared service so the pagination component can independently subscribe and react without relying on @Input() timing at all.

// pagination-state.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class PaginationStateService {
  private totalItems$ = new BehaviorSubject<number>(0);
  totalItems = this.totalItems$.asObservable();

  setTotalItems(total: number) {
    this.totalItems$.next(total);
  }
}
// worklist.component.ts
this.paginationState.setTotalItems(result.total);

// pagination.component.ts
totalItems$ = this.paginationState.totalItems;
// template: 

Using a shared service with observables is especially useful for deeply nested components, avoiding the need to bubble data and events up and down the component tree via @Input/@Output chains.


Summary — Which Option to Pick?

Situation Best Fix
Angular 17+ / 21 modern project Signals + input()
Existing @Input() pattern, can’t refactor ChangeDetectorRef.detectChanges() in child
Parent controls the update markForCheck() in parent
Deeply nested or shared state Shared BehaviorSubject service

The core principle: never rely on a user interaction to accidentally trigger change detection. Either use reactive primitives (Signals, Observables) or explicitly schedule a CD check after async data arrives.

References:


Back Donate