Customize CDK stepper not working after upgrade from Angular 9 to 10

3.2k views Asked by At

Recently, I followed the angular update Guide here from Angular 9 to 10. I updated core, cli, material, cdk, ngrx etc. and I did an 'ng update' to make sure everything has migrated properly. Then I complied and built it without any errors/warnings, everything works fine, except the cdk stepper, it doesn't show any of the cdk step and content under <cdk-stepper></cdk-stepper>

Image : Angular 10

But it is working fine in Angular 9

Image: Angular 9

Here is my customise cdk stepper component .ts and html:

import { animate, AnimationEvent, state, style, transition, trigger } from '@angular/animations';
import { CdkStepper, StepContentPositionState } from '@angular/cdk/stepper';
import { AfterContentInit, ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { Subject } from 'rxjs/internal/Subject';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';

@Component({
    selector: 'cdk-stepper',
    templateUrl: './cdk-stepper.component.html',
    styleUrls: ['./cdk-stepper.component.scss'],
    providers: [{ provide: CdkStepper, useExisting: CdkStepperComponent }],
    animations: [trigger('stepTransition', [
        state('previous', style({
            transform: 'translateY(-100%)', zIndex: -1,
            opacity: 0
        })),
        state('current', style({
            transform: 'translateY(0)', zIndex: 0,
            opacity: 1
        })),
        state('next', style({
            transform: 'translateY(100%)', zIndex: -1,
            opacity: 0
        })),
        transition('* => *', animate('700ms cubic-bezier(0.35,0,0.25,1)'))
    ])],
    changeDetection: ChangeDetectionStrategy.OnPush,
})

export class CdkStepperComponent extends CdkStepper
    implements AfterContentInit {

    currentVerticalLine = {
        'height': '20vh',
        'transition': 'height 200ms ease',
        'top': '-3%'
    }

    @Output() readonly animationDone: EventEmitter<void> = new EventEmitter<void>();
    _animationDone = new Subject<AnimationEvent>();

    @Input() stepHeaderNumber;
    stepHeaderTitle = ['Scan Tenant', 'Mailbox Rules', 'Audit Logs', 'Summary']

    @Input('setSelectedIndex') set setSelectedIndex(index: number) {
        this.updateIndex(index);
    }

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

    updateIndex(index: number) {
        console.log(index)
        this.selectedIndex = index;
        this.updateMyIndex.emit(index);
    }

    onClick(index: number): void {
        this.selectedIndex = index;
    }

    ngAfterContentInit() {
        console.log(this.steps)
        // Mark the component for change detection whenever the content children query change
        this.steps.changes.pipe(takeUntil(this._destroyed)).subscribe(() => {
            this._stateChanged();
        });

        this._animationDone.pipe(
            // distinctUntilChanged to avoid emitting the same event twice,
            // as some browsers callback invokes '.done' twice.
            distinctUntilChanged((x, y) => {
                return x.fromState === y.fromState && x.toState === y.toState;
            }),
            takeUntil(this._destroyed)
        ).subscribe(event => {
            if ((event.toState as StepContentPositionState) === 'current') {
                this.animationDone.emit();
            }
        });
    }

    checkProgress(index) {
        if (this.selectedIndex > index) {
            return true;
        }
        else {
            return false;
        }
    }
}
<div class="container">

    <app-vertical-steps [steps]="steps" [titles]="stepHeaderTitle" [(current)]="selectedIndex" (currentChange)="updateIndex($event)"></app-vertical-steps>

    <div class="cdk-vertical-content-container">
        <div *ngFor="let step of steps; let i=index;" class="cdk-vertical-content ng-trigger ng-trigger-stepTransition"
             [@stepTransition]="_getAnimationDirection(i)" [attr.tabindex]="selectedIndex === i ? 0 : null"
            [id]="_getStepContentId(i)" (@stepTransition.done)="_animationDone.next($event)"
            [attr.aria-labelledby]="_getStepContentId(i)" [attr.aria-expanded]="selectedIndex === i">
            <mat-card class="cdk-card" [class.hidden]="selectedIndex !== i">
                <ng-container [ngTemplateOutlet]="step.content"></ng-container>
            </mat-card>
        </div>
    </div>

</div>

And the following is my parent component container that using the cdk-stepper as a child:

import { Component, OnDestroy, OnInit } from "@angular/core";
import { select, Store } from '@ngrx/store';
import { combineLatest } from 'rxjs';
import { GraphActions } from 'src/app/actions';
import { fetchSummaries } from 'src/app/entities/event-summary/event-summary.actions';
import * as fromRoot from 'src/app/reducers';


@Component({
    selector: "app-container",
    templateUrl: "./container.component.html",
    styleUrls: ["./container.component.scss"]
})
export class ContainerComponent implements OnInit, OnDestroy {
    pending = false;
    myIndex = 0;
    constructor(
        private store: Store<fromRoot.State>
    ) { }

    ngOnInit() {

        combineLatest(
            this.store.pipe(select(fromRoot.getInitialized)),
            this.store.pipe(select(fromRoot.inboxRulesFetched)),
            this.store.pipe(select(fromRoot.getUsers))
        ).subscribe(([initialized, fetched, users]) => {
            if (initialized) {
                this.store.dispatch(fetchSummaries());
                if (!fetched) {
                    for (let index = 0; index < users.length; index++) {
                        const identity = users[index].id;
                        const mail = users[index].mail;
                        if (mail !== null && users[index].userType !== 'Guest') {
                            this.store.dispatch(GraphActions.fetchGraphInboxRules({ identity }))
                        }
                    }
                }
            }
        })
    }

    ngOnDestroy() {
    }

    setIndex($event) {
        this.myIndex = $event;
    }
    setPendingScan($event) {
        this.pending = $event;
    }
}
<div class="container">
    <app-log-scanning-icon [pendingScan]="pending"></app-log-scanning-icon>
    <cdk-stepper [setSelectedIndex]="myIndex" (updateMyIndex)="setIndex($event)">
        <cdk-step>
            <app-introduction (indexChange)="setIndex($event)" (pendingScan)="setPendingScan($event)"></app-introduction>
        </cdk-step>
        <cdk-step>
            <app-mailboxes (indexChange)="setIndex($event)" [currentStep]="myIndex === 1"></app-mailboxes>
        </cdk-step>
        <cdk-step>
            <app-audit-logs (indexChange)="setIndex($event)" [currentStep]="myIndex === 2"></app-audit-logs>
        </cdk-step>
        <cdk-step>
            <app-summary (indexChange)="setIndex($event)" [currentStep]="myIndex === 3"></app-summary>
        </cdk-step>
    </cdk-stepper>
</div>

I will be appreciated if anyone can help or providing any hints why it doesn't work on angular 10.. Or any part of my code is delegrated? Thanks.

P.S. here is my package.json version list:package.json

2

There are 2 answers

2
VincD On BEST ANSWER

Faced the same problem. The problem is ngAfterContentInit() in your CdkStepperComponent component. In version 10 you have to call super.ngAfterContentInit() of the CdkStepper parent class, the steps are initialized in this method since version 10 apparently.

1
priyanka On

I faced the same issue after upgrading to angular 10. It used to give error, can't access content of undefined. Basically the selected value was not getting set.After multiple attempt to debug the issue , I downgraded the @angular/cdk version to 10.0.0 and it worked. Try downgrading the cdk version. Latest version has issues.