I have a large application made with Angular 12 where in one part of it there is a parent component and a child component.
Parent component (HTML):
<app-sidenav></app-sidenav>
<div class="row fadeIn animated">
<div class="col s12 m-0 p-2 pt-5">
<span class="card-title col s6 pt-4" style="font-size: x-large;">
{{'Tasks' | translate}}
</span>
<div class="col s6">
<app-filter [tasks]="tasks"></app-filter>
</div>
</div>
<div class="col s12">
<mat-tab-group dynamicHeight mat-align-tabs="center" class="p-2 pb-0" (selectedIndexChange)="setTab($event)"
[selectedIndex]="selectedTab" #tabGroup>
<mat-tab *ngIf="!searching" matBadge="7">
<ng-template mat-tab-label>
<div class="mr-5 ml-5 pr-5 pl-5">
{{'Open' | translate}}
</div>
<span *ngIf="openTasks.length > 0" class="btn btn-small btn-floating blue lighten-3 pulse">
{{openTasks.length}}
</span>
</ng-template>
<div class="card m-1">
<app-queue-table [dataSourceInput]="openTasks"></app-queue-table>
</div>
</mat-tab>
<mat-tab *ngIf="!searching">
<ng-template mat-tab-label>
<div class="mr-5 ml-5 pr-5 pl-5">
{{'In Progress' | translate}}
</div>
<span *ngIf="inProgressTasks.length > 0" class="btn btn-small btn-floating weg-blue darken-4"> {{inProgressTasks.length}}
</span>
</ng-template>
<div class="card m-1">
<app-queue-table [dataSourceInput]="inProgressTasks"></app-queue-table>
</div>
</mat-tab>
<mat-tab *ngIf="!searching">
<ng-template mat-tab-label>
<div class="mr-5 ml-5 pr-5 pl-5">
{{'Finished' | translate}}
</div>
<span *ngIf="finishedTasks.length > 0"
class="btn btn-small btn-floating green lighten-4 weg-blue-text text-darken-4">
{{finishedTasks.length}}
</span>
</ng-template>
<div class="card m-1">
<app-queue-table [dataSourceInput]="finishedTasks"></app-queue-table>
</div>
</mat-tab>
<mat-tab *ngIf="!searching">
<ng-template mat-tab-label>
<div class="mr-5 ml-5 pr-5 pl-5">
{{'Canceled' | translate}}
</div>
<span *ngIf="canceledTasks.length > 0" class="btn btn-small btn-floating red lighten-1">
{{canceledTasks.length}} </span>
</ng-template>
<div class="card m-1">
<app-queue-table [dataSourceInput]="canceledTasks"></app-queue-table>
</div>
</mat-tab>
<mat-tab *ngIf="searching" class="p-4">
<ng-template mat-tab-label>
<div class="mr-5 ml-5 pr-5 pl-5">
{{'Searching' | translate}}
</div>
<span *ngIf="search > 0" class="btn btn-small btn-floating disabled"> {{search}} </span>
</ng-template>
<div class="card p-3">
<app-queue-table [dataSourceInput]="canceledTasks"></app-queue-table>
</div>
</mat-tab>
</mat-tab-group>
</div>
</div>
Parent component (TypeScript):
import { Component, OnInit, ViewChild } from '@angular/core'
import { MatTabGroup, MatTabHeaderPosition } from '@angular/material/tabs';
import { Router } from '@angular/router';
import { ModTask } from 'src/app/core/models/modTask.model';
import { UserService } from 'src/app/core/services/user.service';
import { ModTaskService } from 'src/app/shared/services/modTask.service';
@Component({
selector: "app-home",
templateUrl: "./home.component.html",
styleUrls: ["./home.component.css"]
})
export class HomeComponent implements OnInit{
public tasks: ModTask[]
public openTasks: ModTask[]
public inProgressTasks: ModTask[]
public finishedTasks: ModTask[]
public canceledTasks: ModTask[]
public searching = false;
public open: number = 0;
public inProgress: number = 0;
public finished: number = 0;
public canceled: number = 0;
public working: number = 0;
public search: number = 0;
public selectedTab: number;
public coordinator: string
private cacheName = "165116511s1a";
@ViewChild('tabGroup') tabGroup: MatTabGroup;
constructor(
private router: Router,
private modTaskService: ModTaskService,
private userService: UserService
) {}
async ngOnInit(): Promise<void> {
this.userService.getLoggedUser().subscribe(user => { this.coordinator = user.userLogin })
this.tasks = await this.modTaskService.getByCoordinator(this.coordinator)
this.openTasks = this.tasks.filter(tasks => tasks.status.status === "Open")
this.inProgressTasks = this.tasks.filter(tasks => tasks.status.status === "In Progress")
this.finishedTasks = this.tasks.filter(tasks => tasks.status.status === "Finished")
this.canceledTasks = this.tasks.filter(tasks => tasks.status.status === "Canceled")
console.log(this.openTasks)
}
async ngAfterViewInit() {
console.log('atualizado')
}
setTab(tab: number) {
this.ngAfterViewInit()
}
// ACTIONS =========================================================
setSearch(event: boolean) {
this.searching = event;
localStorage.setItem(this.cacheName + "as", event.toString());
}
// QUEUE COUNTS ====================================================
setResultLength(type: number, resultLength: number = 0) {
switch (type) {
case 1:
this.open = resultLength;
break;
case 2:
this.inProgress = resultLength;
break;
case 3:
this.finished = resultLength;
break;
case 4:
this.canceled = resultLength;
break;
case 5:
this.open = 0;
this.inProgress = 0;
this.finished = 0;
this.search = resultLength;
break;
}
}
}
Child component (HTML):
<div class="row fadeIn animated m-0 p-0 table-wrapper" style="min-height:480px">
<!-- <div class="col s12 vertical-center" style="height:100%" *ngIf="dataSource.length === 0">
<span class="material-icons large grey-text text-lighten-3"
style="margin-right: auto; margin-left: auto;">inbox</span>
</div> -->
<!--COM RESULTADOS-->
<table mat-table [dataSource]="dataSourceInput">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef> ID </th>
<td mat-cell *matCellDef="let task" (click)="openRequest(task)"> {{task.id}} </td>
</ng-container>
<ng-container matColumnDef="branch">
<th mat-header-cell *matHeaderCellDef> Branch </th>
<td mat-cell *matCellDef="let task" (click)="openRequest(task)"> {{task.branchId}} </td>
</ng-container>
<ng-container matColumnDef="createdBy">
<th mat-header-cell *matHeaderCellDef> Created By </th>
<td mat-cell *matCellDef="let task" (click)="openRequest(task)"> {{task.creatorUserName}} </td>
</ng-container>
<ng-container matColumnDef="salesDocument">
<th mat-header-cell *matHeaderCellDef> Sales Document </th>
<td mat-cell *matCellDef="let task" (click)="openRequest(task)"> {{task.salesDocument}} </td>
</ng-container>
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef> Created At </th>
<td mat-cell *matCellDef="let task" (click)="openRequest(task)"> {{task.createdAt}} </td>
</ng-container>
<ng-container matColumnDef="updatedAt">
<th mat-header-cell *matHeaderCellDef> Updated At </th>
<td mat-cell *matCellDef="let task" (click)="openRequest(task)"> {{task.updatedAt}} </td>
</ng-container>
<ng-container matColumnDef="target">
<th mat-header-cell *matHeaderCellDef> Target </th>
<td mat-cell *matCellDef="let task" (click)="openRequest(task)"> {{task.target}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator [pageSizeOptions]="[5, 10, 20]"
showFirstLastButtons
aria-label="Select page of periodic elements">
</mat-paginator>
</div>
Child component (TypeScript):
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
import { Router } from '@angular/router'
import { ModTask } from 'src/app/core/models/modTask.model'
import { QueueTable } from 'src/app/shared/components/queue-table/queue-table.component'
import {MatPaginator} from '@angular/material/paginator'
import { MatTableDataSource } from '@angular/material/table'
@Component({
selector: 'app-queue-table',
templateUrl: './task-queue.component.html',
styleUrls: ['./task-queue.component.css']
})
export class QueueTableComponent {
@Input() public dataSourceInput: ModTask[]
displayedColumns: string[] = ['id', 'branch', 'createdBy', 'salesDocument', 'createdAt', 'updatedAt', 'target']
ngAfterViewInit(): void {
console.log(this.dataSourceInput)
}
constructor(
private router: Router
) {
}
public openRequest(modTask: ModTask) {
this.router.navigate([`moduleTask/process/${modTask.id}`])
}
}
As you can see, I fetch data in my API in the parent component, do the filtering that needs to be done and put this data as an Input in the child component. And in the child component, I show this data on screen using Angular’s Material Table. The data is shown as I would like, but when that console.log() in ngAfterViewInit is executed, it returns undefined, and I need to do some work on this data to filter, but I can’t because it’s set to undefined, even showing the data on screen.
I wanted to know why this happens and what can be done to return the data correctly in the browser console.
I wanted to know why this happens and what can be done to return the data correctly in the browser console.
I’ve tried using @Input() in a few different ways, but without success.
>Solution :
The value you’re using for the input is fetched asynchronously. Your input data is shown, because after your asynchronous call to this data is set, change detection runs and updates it to the returned value. You can test this by changing your child lifecycle hook from ngAfterViewInit (which runs once) to ngAfterViewChecked (which runs after every view check). You will see it will now log undefined (your initial value) then your updated view value.
An easy fix for this is just to put an ngIf on your child until tasks is set, e.g. <app-queue-table *ngIf="tasks" [dataSourceInput]="inProgressTasks"></app-queue-table>
This way your child only gets instantiated when the data already exists, so the initial value passed in the input will always be defined. As a heads up here, if you were using strict TypeScript checking and had typed your variables accordingly, your IDE would have warned you that you were trying to set a value of "X | undefined" to something that should only be "X".