Search code examples
angularangular-changedetection

UI is not updating the index in Angular child component when trying to change it from the parent component. What am I missing?


I'm trying to update the page index from the parent component in an Angular application using a child component that handles pagination. The child component has an index input property and an event emitter for page changes. However, when I try to update the index from the parent, the UI does not reflect the updated value, and the pagination does not change.My project is on Angular 13.

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

@Component({
  selector: 'numbered-pagination',
  templateUrl: './numbered-pagination.component.html',
  styleUrls: ['./numbered-pagination.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class NumberedPaginationComponent implements OnChanges {
  @Input() index: number = 1;
  @Input() totalCount: number = 0;
  @Input() pageSize: number = 50;
  @Input() rulerLength: number = 5;

  @Output() page: EventEmitter<number> = new EventEmitter<number>();

  maxPages: number = 1;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['totalCount'] || changes['pageSize']) {
      this.calculateMaxPages();
    }
  }

  private calculateMaxPages(): void {
    this.maxPages = Math.ceil(this.totalCount / this.pageSize) || 1;
  }

  get pagination(): NumberedPagination {
    const { index, maxPages, rulerLength } = this;
    const pages = ruler(index, maxPages, rulerLength);
    return { index, maxPages, pages };
  }

  navigateToPage(pageNumber: number): void {
    if (allowNavigation(pageNumber, this.index, this.maxPages)) {
      this.index = pageNumber;
      this.page.emit(this.index);
      console.log("Index  =>> " + this.index);
    }
    console.log("Index1  =>> " + this.index);
  }

  trackByFn(index: number): number {
    return index;
  }

  constructor(private cdr: ChangeDetectorRef) {}

  refreshToInitial() {
    this.navigateToPage(1);
    this.cdr.detectChanges();
    this.cdr.markForCheck();
  }
}


function generatePageArray(rulerLength: number): number[] {
  const result: number[] = [];
  for (let i = 1; i <= rulerLength; i++) {
    result.push(i);
  }
  return result;
}
const ruler = (
  currentIndex: number,
  maxPages: number,
  rulerLength: number
): number[] => {
  if (maxPages <=rulerLength) {
    return generatePageArray(maxPages);; // If maxPages is 1, return an array with only 1 page
  }
  const array = new Array(rulerLength).fill(null);
  const min = Math.floor(rulerLength / 2);

  return array.map((_, index) =>
    rulerFactory(currentIndex, index, min, maxPages, rulerLength)
  );
};

const rulerOption = (
  currentIndex: number,
  min: number,
  maxPages: number
): RulerFactoryOption => {
  return currentIndex <= min
    ? RulerFactoryOption.Start
    : currentIndex >= maxPages - min
    ? RulerFactoryOption.End
    : RulerFactoryOption.Default;
};

const rulerFactory = (
  currentIndex: number,
  index: number,
  min: number,
  maxPages: number,
  rulerLength: number
): number => {
  const factory = {
    [RulerFactoryOption.Start]: () => index + 1,
    [RulerFactoryOption.End]: () => maxPages - rulerLength + index + 1,
    [RulerFactoryOption.Default]: () => currentIndex + index - min,
  };

  return factory[rulerOption(currentIndex, min, maxPages)]();
};

const allowNavigation = (
  pageNumber: number,
  index: number,
  maxPages: number
): boolean => {
  return pageNumber !== index && pageNumber > 0 && pageNumber <= maxPages;
};

HTML part of Child Component

<ol *ngIf="maxPages>1" class="paginator-container">
  <li (click)="navigateToPage(1)">
    <i matTooltip="First Page" class="fa-solid fa-angles-left"></i>&nbsp;
      <span>First</span>
  </li>
  <li (click)="navigateToPage(index - 1)"><i matTooltip="Previous Page" class="fa-solid fa-angle-left"></i>&nbsp;<span>Previous</span></li>
  <li
    *ngFor="let page of pagination.pages; trackBy: trackByFn"
    class="paginator-number"
    [class.active]="page === pagination.index"
    (click)="navigateToPage(page)"
    
    >
    {{ page }}
  </li>
  <li (click)="navigateToPage(index + 1)"><span>Next</span>&nbsp;<i matTooltip="Next Page" class="fa-solid fa-angle-right"></i></li>
  <li (click)="navigateToPage(maxPages)">
    <span>Last</span>&nbsp;
    <i matTooltip="Last Page" class="fa-solid fa-angles-right"></i>
</li>
</ol>

Parent Component TypeScript

<button class="edit_button" (click)="onAssignToButtonClick('Extracted')">
  Assign To
</button>

<numbered-pagination [index]="extractedPageNumber" (page)="pageEvent('New', $event)" [pageSize]="pageSize" [totalCount]="pageNumberExtractedLimit"></numbered-pagination>

Problem:

  1. I'm trying to update the page index from the parent component using the refreshPaginationToInitial method, but the child component's pagination UI doesn't reflect the updated index.
  2. The child component uses ChangeDetectionStrategy.OnPush and is not updating the UI when the index value changes in the parent.

What I've tried:

  1. I’m using ChangeDetectorRef in the child component to manually trigger change detection, but it doesn’t seem to update the UI.

  2. The child’s pagination logic correctly emits page changes via the page output, but the UI doesn't react to the changes.

Can someone explain why this isn't working or suggest a solution?


Solution

  • Thanks to Pieterjan’s reply, I tried implementing two-way binding. However, to fix the issue, I had to create a new component with the same logic. Here’s the change I made :

      @Output() page: EventEmitter<number> = new EventEmitter<number>();
    

    With this:

      @Output() indexChange: EventEmitter<number> = new EventEmitter<number>();
    

    Despite this change, it didn’t work in the old component, and I had to create a new pagination component.

    I’m not sure why this change didn’t take effect in the existing component. Does anyone know why this might happen?