Angular 'View Child' only working with template reference variable and not with class name

1.1k Views Asked by At

I created a simple paginator component that I use all over my app. I am using view child to access some of the properties; however I encountered a weird bug with View Child.

When I use the component class name (PaginatorComponent) as the selector for view child, it is undefined. However, when I switch to a template reference variable everything works as expected. The bug only shows up in the deployed version of my app: everything worked fine locally (even in production mode) and I haven't been able to replicate it in a Stackblitz.

I am using Angular and Angular Material, and the deployed app is running with other micro-frontends using single-spa/angular.

I have searched and searched but can find nothing helpful. Does anyone know of a nuance in Angular / View Child that would cause this issue? I am able to get around the issue by using template reference variables, but it bothers me that I cannot figure out why using the class name doesn't work.

Paginator ts

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { paginationConfigs } from 'configs';

export interface PageChangeEvent {
  pageNumber: string;
  pageLength: string;
}

@Component({
  selector: 'app-paginator',
  templateUrl: './paginator.component.html',
  styleUrls: ['./paginator.component.css'],
})
export class PaginatorComponent implements OnInit {
  @Input() viewingLastPage: boolean;

  public _pageIndex = 0;
  get pageIndex(): number {
    return this._pageIndex;
  }
  set pageIndex(val: number) {
    this._pageIndex = val;
    this.pageNumberChange.emit({
      pageLength: this.pageLength,
      pageNumber: val.toString(),
    });
  }

  public _pageLength = paginationConfigs.pageLengthOptions[0];
  get pageLength(): string {
    return this._pageLength;
  }
  set pageLength(val) {
    this._pageLength = val;
    this.pageLengthChange.emit({
      pageLength: val,
      pageNumber: this.pageIndex.toString(),
    });
  }
  public pageLengthOptions: string[] = paginationConfigs.pageLengthOptions;
  @Output() pageNumberChange: EventEmitter<PageChangeEvent> = new EventEmitter<
    PageChangeEvent
  >();
  @Output() pageLengthChange: EventEmitter<PageChangeEvent> = new EventEmitter<
    PageChangeEvent
  >();
  @Output() initialized: EventEmitter<PageChangeEvent> = new EventEmitter<PageChangeEvent>();

  constructor() {}

  ngOnInit(): void {
    this.emitInitialized();
  }

  changePage(change: number): void {
    this.pageIndex += change;
  }

  emitPageNumberChange(): void {
    this.pageNumberChange.emit({
      pageNumber: this.pageIndex.toString(),
      pageLength: this.pageLength,
    });
  }

  emitInitialized(): void {
    this.initialized.emit({
      pageNumber: this.pageIndex.toString(),
      pageLength: this.pageLength
    });
  }
}

paginator html

<div class="paginator">
    <mat-form-field class="length-dropdown">
        <mat-label>Page Length</mat-label>
        <mat-select [(value)]="this.pageLength">
            <mat-option *ngFor="let length of pageLengthOptions" [value]="length">{{length}}</mat-option>
        </mat-select>
    </mat-form-field>
    <button mat-icon-button class="previous-page-navigate" (click)="changePage(-1)" [disabled]="this.pageIndex === 0">
        <mat-icon>keyboard_arrow_left</mat-icon>
      </button>
      <span>{{ this.pageIndex + 1 }} </span>
      <button mat-icon-button class="next-page-navigate" (click)="changePage(+1)" [disabled]="viewingLastPage">
        <mat-icon>keyboard_arrow_right</mat-icon>
      </button>
</div>

parent ts

export class WebsiteConfigComponent implements OnInit {
  @ViewChild(WebsiteListTableComponent) table: WebsiteListTableComponent;
  @ViewChild(PaginatorComponent) paginator: PaginatorComponent;

  ngAfterViewInit(): void {
    this.loadWebsites();
  }

  loadWebsites() {
    this.getWebsiteList(this.paginator.pageLength, this.paginator.pageIndex.toString());
  }
...

parent html

    <div class="website-filters">
  <mat-form-field>
    <mat-label>Website</mat-label>
    <input matInput [(ngModel)]="searchString" autocomplete="off" />
  </mat-form-field>
  <button mat-raised-button (click)="searchWebsites()">Search</button>
  <button mat-raised-button (click)="resetFilters()">Reset</button>
  <button mat-raised-button (click)="createNewWebsite()">
    Create New Website
  </button>
  <button mat-raised-button routerLink="/app/configurations">Back</button>
</div>
<div class="websites-table">
  <app-website-list-table
    [websites]="websites"
    (viewWebsite)="editWebsite($event)"
    [ngClass]="{'table-loading': this.loading$ | async}"
  ></app-website-list-table>
</div>
<div class="spinner" *ngIf="this.loading$ | async">
  <mat-spinner></mat-spinner>
</div>
<app-paginator #paginator [viewingLastPage]="endOfList" (pageNumberChange)="onPageNumberChange($event)"></app-paginator>
1

There are 1 best solutions below

3
On

Please paste your parent ts code for using paginator part, since there is one thing to consider is AfterViewInit, you need to wait for the view to be initialized before you can access your @ViewChild.