Search code examples
angulartypescriptangular-materialviewchild

view child component undefined


I have a parent component(DepotSelectionComponent) and a child component(SiteDetailsComponent). An event(moreDetails) is emitted to the parent component. This event then calls the getDetailsPage()function within the parent, which changes the page using ngswitch and also loads some data. However the component what i am trying to load seems to be undefined therefore the references to that component are not working via the viewchild decorator.

I am sure this has something to do with the ngswitch however i just cant seem to figure out how to fix it, i have tried adding timeouts. The populateResults function works due to that component already being loaded however the populateDepotResults dont work due to the component being undefined and not loaded by the switch yet.

parent(DepotSelectionComponent) html:

    <div class="main">  

    <div [ngSwitch]="page">

        <div *ngSwitchCase="'siteLandingPage'">
            <div class="interactiveMap">
                <app-interactive-map (siteDetailTable)="populateResults($event)"></app-interactive-map>
            </div>
            <div class="mapResult">
                <app-map-result (moreDetails)="getDetailsPage($event)"></app-map-result>
            </div>
        </div>

        <div *ngSwitchCase="'siteMoreDetails'">
            <div>
                <button mat-raised-button (click)="home()">Back</button>
            </div>
            <div class="clearFloat"></div>
            <app-site-details [siteDetailsar]="siteDetailsar" (depotMoreDetails)="depotMoreDetails($event)"></app-site-details>
        </div>

        <div *ngSwitchCase="'depotMoreDetails'">
            <div>
                <button mat-raised-button (click)="getDetailsPage()">Back</button>
            </div>
            <div class="clearFloat"></div>
            <app-depot-parent></app-depot-parent>
        </div>
    </div>

</div>

parent(DepotSelectionComponent) ts:

    import {  Component, 
          OnInit,
          ViewChild, 
          ChangeDetectorRef } from '@angular/core';
import { MapResultComponent } from '../map-result/map-result.component';
import { SiteService } from '../shared/services/site-service';
import { siteDetails } from '../shared/interfaces/site-details.interface';
import { MatTableDataSource } from '@angular/material';
import { DepotService } from '../shared/services/depot-service';
import { depotDetails } from '../shared/interfaces/depot-details.interface';
import { SiteDetailsComponent } from '../site-details/site-details.component';

@Component({
  selector: 'app-depot-selection',
  templateUrl: './depot-selection.component.html',
  styleUrls: ['./depot-selection.component.scss']
})
export class DepotSelectionComponent implements OnInit {
  siteDetailsar: Array<{Site_ID: number, Address1: string, Address2: string, Town: string, County: string, PostCode: string, Name: string, Description: string, TelephoneNumber: string}> = [];
  //depotResults: Array<{Depot_ID:number, Site_ID: number, Address1: string, Address2: string, Town: string, County: string, PostCode: string, Name: string, Description: string, TelephoneNumber: string}> = [];

  page:string;
  countyName: string;
  @ViewChild(MapResultComponent, {static: false})
  private MapResultComponent: MapResultComponent;

  @ViewChild(SiteDetailsComponent, {static: false})
  private SiteDetailsComponent: SiteDetailsComponent;

  constructor(
    private _siteService: SiteService,
    private _depotService: DepotService,
    private changeDetectorRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.page = "siteLandingPage";console.log('on init', this.SiteDetailsComponent);
    // this returns undefined
  }

  ngAfterViewInit() {
      console.log('on after view init', this.SiteDetailsComponent);
      // this returns null
  }

  home() {
    this.page = "siteLandingPage";
  }

  getDetailsPage(event) {
    this.page = "siteMoreDetails";
    var target = event.target || event.srcElement || event.currentTarget;
    var idAttr = target.attributes.id;

    this.getSiteDetailsByID(event.target.id);
    this.populateDepotResults(event.target.id);

    this.changeDetectorRef.detectChanges();  
  }

  depotMoreDetails() {
    this.page = "depotMoreDetails"
  }

 getSiteDetailsByID(id: number) {
    this.siteDetailsar.length = 0;
    this._siteService.getSiteByID(id)
    .subscribe((data: any[]) => {
      data.forEach(e => {
        this.siteDetailsar.push(new siteDetails(e.Site_ID, e.Address1, e.Address2, e.Town, e.County, e.PostCode, e.Name, e.Description, e.TelephoneNumber));
      })  
    });
  }

  populateDepotResults(id: number) {
    console.log(this.SiteDetailsComponent);
    this.SiteDetailsComponent.depotResults.length = 0;
    this._depotService.getAllDepots(id)
    .subscribe((data: any[]) => {
      data.forEach(e => {
        this.SiteDetailsComponent.depotResults.push(new depotDetails(e.Depot_ID ,e.Site_ID, e.Address1, e.Address2, e.Town, e.County, e.PostCode, e.Name, e.Description, e.TelephoneNumber));
      })   
      this.SiteDetailsComponent.dataSource = new MatTableDataSource(this.SiteDetailsComponent.depotResults);
      this.SiteDetailsComponent.dataSource.paginator = this.SiteDetailsComponent.paginator; 
      this.changeDetectorRef.detectChanges();  
    }); 
}

  populateResults(countyName) {
    this.MapResultComponent.mapResultHeader = countyName.dataObj.label;
    this.MapResultComponent.siteResults.length = 0;
    this._siteService.getSites(countyName.dataObj.label)
    .subscribe((data: any[]) => {
      data.forEach(e => {
      this.MapResultComponent.siteResults.push(new siteDetails(e.Site_ID, e.Address1, e.Address2, e.Town, e.County, e.PostCode, e.Name, e.Description, e.TelephoneNumber));
      })   
      this.MapResultComponent.dataSource = new MatTableDataSource(this.MapResultComponent.siteResults);
      this.MapResultComponent.dataSource.paginator = this.MapResultComponent.paginator; 
      this.changeDetectorRef.detectChanges();  
    }); 
}

}

child(SiteDetailsComponent) html:

<div class="siteInfo">
    <div class="depotResultHeader">
        <span>Site Information</span>
    </div>
    <mat-list>
        <h3 mat-subheader>General Information</h3>
        <mat-list-item >Site Name: {{ siteDetailsar[0].Name }} </mat-list-item>
        <mat-divider></mat-divider>
        <mat-list-item>Site Description: {{ siteDetailsar[0].Description }}</mat-list-item>
        <mat-divider></mat-divider>
        <mat-list-item>Address1: {{ siteDetailsar[0].Address1 }}</mat-list-item>
        <mat-divider></mat-divider>
        <mat-list-item>Address2: {{ siteDetailsar[0].Address2 }}</mat-list-item>
        <mat-divider></mat-divider>
        <mat-list-item>Town: {{ siteDetailsar[0].Town }}</mat-list-item>
        <mat-divider></mat-divider>
        <mat-list-item>County: {{ siteDetailsar[0].County }}</mat-list-item>
        <mat-divider></mat-divider>
        <mat-list-item>Postcode: {{ siteDetailsar[0].PostCode }}</mat-list-item>
        <mat-divider></mat-divider>
        <mat-list-item>Telephone Number: {{ siteDetailsar[0].TelephoneNumber }}</mat-list-item>
        <mat-divider></mat-divider>
    </mat-list>

    <div class="siteButtons">    
        <button mat-raised-button (click)="editSiteDialog()">Edit Site Details</button>
        <button class="remove" mat-raised-button>Remove Site</button>
    </div>

</div>

<div class="depotLocations">
    <div class="depotResultHeader">
        <span>List Of Depots</span>
    </div>
    <div class="mat-elevation-z8">
        <mat-form-field class="filter">
            <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
        </mat-form-field>

        <table mat-table [dataSource]="dataSource">

        <ng-container matColumnDef="name">
            <th mat-header-cell *matHeaderCellDef> Depot Name </th>
            <td mat-cell *matCellDef="let element"> {{element.Name}}</td>
        </ng-container>

        <ng-container matColumnDef="address">
            <th mat-header-cell *matHeaderCellDef> Address </th>
            <td mat-cell *matCellDef="let element"> {{element.Address1}}</td>
        </ng-container>

        <ng-container matColumnDef="moreDetails">
            <th mat-header-cell *matHeaderCellDef> More Details </th>
            <td mat-cell *matCellDef="let element"> 
                <button mat-raised-button (click)="depotMoreDetailsPage($event)">More Details</button> 
            </td>
        </ng-container>

        <ng-container matColumnDef="remove">
            <th mat-header-cell *matHeaderCellDef> Delete </th>
            <td mat-cell *matCellDef="let element"> 
                <button class="remove" mat-raised-button>Remove</button> 
            </td>
        </ng-container>

        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
        </table>

        <mat-paginator [pageSize]="5" showFirstLastButtons></mat-paginator>
    </div>

    <div class="addNewDepot">
        <button mat-raised-button>Add New Depot</button>
    </div>
</div>

child(SiteDetailsComponent) ts:

import { Component, OnInit, ViewChild, Output, EventEmitter, Input } from '@angular/core';
import { MatTableDataSource, MatPaginator, MatDialog, MatDialogConfig } from '@angular/material';
import { SiteInformationDialogComponent } from '../site-information-dialog/site-information-dialog.component';
import { SiteService } from '../shared/services/site-service';
import { siteDetails } from '../shared/interfaces/site-details.interface';

@Component({
  selector: 'app-site-details',
  templateUrl: './site-details.component.html',
  styleUrls: ['./site-details.component.scss']
})
export class SiteDetailsComponent implements OnInit {

  constructor(public dialog: MatDialog) { }

  displayedColumns: string[] = ['name', 'address', 'moreDetails', 'remove'];
  number: number;
  result : string;
  mapResults: Array<{name: string, town: string, address: string}> = [];
  @Input() siteDetailsar: Array<{Site_ID: number, Address1: string, Address2: string, Town: string, County: string, PostCode: string, Name: string, Description: string, TelephoneNumber: string}> = [];
  depotResults: Array<{Depot_ID: number, Site_ID: number, Address1: string, Address2: string, Town: string, County: string, PostCode: string, Name: string, Description: string, TelephoneNumber: string}> = [];
  dataSource : MatTableDataSource<any>;

  @Output() depotMoreDetails = new EventEmitter();

  @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;

  ngOnInit() {
    this.dataSource = new MatTableDataSource();
    console.log(this.depotResults);
  }
    /**
   * Set the paginator and sort after the view init since this component will
   * be able to query its view for the initialized paginator and sort.
   */
  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
  }

  applyFilter(filterValue: string) {
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }

  editSiteDialog(){
    const dialogConfig = new MatDialogConfig();

    dialogConfig.autoFocus = true;
    dialogConfig.disableClose = true;

    this.dialog.open(SiteInformationDialogComponent, dialogConfig);
  }

  depotMoreDetailsPage(changed: string) {
    this.depotMoreDetails.emit(changed);
  }
}

export interface Data {
  name: string;
  town: string;
  address: string;
  telephone: string;
}

Solution

  • First change @ViewChild() to @ViewChildren() and use QueryList<> and then subscribe to changes of. so, you can get wait and load component only when is template loaded properly for component..

    export class DepotSelectionComponent implements AfterViewInit  
    {
    
         @ViewChildren(SiteDetailsComponent ) childrenComponent: QueryList<SiteDetailsComponent >;
    
        public ngAfterViewInit(): void
        { 
            this.childrenComponent.changes.subscribe((comps: QueryList<SiteDetailsComponent>) =>
            {
                  // Now you can access to the child component
            });
         }
    }
    

    To checking if component is loaded or not you have to first use @ViewChildren() with QueryList<> and for then you have to subscirbe() for changes in details-component...