0

I have an Angular Material mat-tab-group, with 12 mat-tabs inside.

Inside each mat-tab, I have a particular component with an unique id. My component's tree looks like this:

<mat-tab-group>
    <mat-tab> <app-step-one #stepOne></app-step-one> </mat-tab>
    <mat-tab> <app-step-two #stepTwo></app-step-two> </mat-tab>
    <mat-tab> <app-step-three #stepThree></app-step-three> </mat-tab>
    ...
    <mat-tab> <app-step-twelve #stepTwelve></app-step-twelve> </mat-tab>
</mat-tab-group>

And, in my TS file, I'm referencing each app-step-* component with @ViewChild, and added all of them on an array, this way:

@ViewChild("stepOne") stepOne: StepOneComponent;
@ViewChild("stepTwo") stepTwo: StepTwoComponent;
@ViewChild("stepThree") stepThree: StepThreeComponent;
...
@ViewChild("stepTwelve") stepTwelve: StepTwelveComponent;

tabs[] = [
    this.stepOne,
    this.stepTwo,
    this.stepThree,
    ...
    this.stepTwelve
];

But, for some reason, the properties in my TS file are all undefined, and I can't use them to get my component's properties and methods, either inside and outside the array.

Can someone help me? I can't figure out why isn't working as expected.

RBFraphael
  • 367
  • 1
  • 3
  • 13
  • 1
    Where exactly in your code are you trying to access these properties? Is it possible that's an unbounded event handler? – Octavian Mărculescu May 30 '22 at 09:02
  • [This post](https://stackoverflow.com/questions/34947154/angular-2-viewchild-annotation-returns-undefined) will help you. – N.F. May 30 '22 at 09:05
  • 1
    A mat-tab-group works using `*ngIf`, So your ViewChild don't exist until a tab is selected (and then only one has value). Really I can not imagine what is the reason you need access to all of them at time. You can use the events (selectedTabChange) or (selectedIndexChange) -and inside this events you can access to your element. Else a "more complex" approach can be use some like this [SO](https://stackoverflow.com/questions/68129174/how-to-force-mat-tab-body-to-render-bind-html-element-for-inactive-tab/68129729#68129729) – Eliseo May 30 '22 at 09:20
  • @OctavianMărculescu I'm trying to access them on a validation function for navigation buttons (next/previous), that will check if current tab has valid values (each tab is an independent part, with independent submit and form, of a big registration process) and is successfully submitted. – RBFraphael May 30 '22 at 09:23
  • @Eliseo If the tab content isn't inside a ng-template with matTabContent attribute, all tabs are loaded in the DOM, so, technically, I can access them with an ViewChild. The behaviour you described only happens when I wrap tab's content with an ng-template with matTabContent. – RBFraphael May 30 '22 at 09:25
  • @RBFraphael, I'm afraid that not, If you use F12 to see the DOM, the "mat-tab-body-content" is empty. If you use to validate a Form, works because the FormGroup exist always (a FormGroup needn't any input to exist) – Eliseo May 30 '22 at 09:46
  • @RBFraphael Validation function sounds exactly like unbounded function that has no context about what's going on. Can you please include the code that sets up this validator function in the body of your question? – Octavian Mărculescu May 30 '22 at 09:56

3 Answers3

0

My problem was in Angular's life time. I've tried to populate the tabs array within the construct method. When I moved the population of tabs to ngAfterViewInit method, finally I reached the result I needed.

RBFraphael
  • 367
  • 1
  • 3
  • 13
  • Happy you get it! I imagine works because you needn't use,e.g. app-step-two when you're in the first tab. Your array becomes some like [**something**,undefined,undefined,undefined,....] then [undefined,**something**,undefined,undefined,...] then ... – Eliseo May 30 '22 at 09:53
0

You need to use ngAfterViewInit() Angular hook to get value from @ViewChild elements. See below ngAfterViewInit() { this.tab1 = this.stepOne; console.log('stepOne testConst = ', this.tab1.testConst); }. Here is a sample https://angular-ivy-7qz2xa.stackblitz.io

0

Another solution I've found, and used, that is visually more optimized (and beautiful): I needed to access the child component just to check if a function called status() is returning true or false. So, each @ViewChild, I checked stepOne.status(). To optimize it, I've change this approach to use @Output events on all child components, that can "send" an event to parent. So, my code looks this way now:

step-one.component.ts, step-two.component.ts and step-three.component.ts

@Output() stepStatus: EventEmitter<boolean> = new EventEmitter<boolean>();

ngOnInit(){
    this.stepStatus.emit(false);
}

// Somewhere in my code that makes this step valid:
this.stepStatus.emit(true);

parent.component.html

<app-step-one (stepStatus)="isValid($event)"></app-step-one>
<app-step-two (stepStatus)="isValid($event)"></app-step-two>
<app-step-three (stepStatus)="isValid($event)"></app-step-three>

parent.component.ts

isValid(valid: boolean): void {
    console.log(valid);
}

After some research and tests, I recommend this way as a better solution, if someone needs something like that, because is more solid and stable, and allows you to lazy load tabs wrapping them with <ng-template matTabContent> (@ViewChild made my brain burn to make this work).

RBFraphael
  • 367
  • 1
  • 3
  • 13