Chaining Tasks performed on NgOnInit and NgAfterViewInit

306 views Asked by At

Guys I am seriously going nuts about handling Angular lifecycle hooks correctly. I am not sure where I am mistaking. I have tried to read a lot of things and is struggling with them from around a week now.

My Problem

I have quite a bit of server api calls and some business logic that I am applying during NgOnInIt(). But doing so I am sometimes running in to RegisterationComponent.html:82 ERROR TypeError: Cannot read property 'titleId' of undefined. And at other times I am struggling to initialize a wizard correctly.

I am now going to define you in detail what is it that I am facing:

ngOnInit()

ngOnInit() {
    //First see if it is a redirect request:
    this.route.queryParamMap
    .pipe(
      map(params => params.get('payment')),
      filter(paramVal => paramVal != null),
      filter(paramVal => paramVal == 'success'),
      tap((paymentInd: string) => {
        this.setOrderDetails();
      }),
      untilDestroyed(this)
    ).subscribe((successPayment: string) => {
      //Update the transaction here

      this.regService.updateTransaction(this.registrationTransactionDetails)
      .pipe(
        filter((regUpdate: RegistrationTransaction) => regUpdate != null),
        tap((regUpdate: RegistrationTransaction) => {
          //This is so that user sees the correct template.
          this.showRegConfirmation = true;
          this.showConfirmation = false;
          
          this.wizard = new KTWizard(this.el.nativeElement, {
            startStep: 2
          });

          this.initializeWizardEvents();

          JsBarcode('#barcode', regUpdate.order.orderLine.barcode);
        }),
        untilDestroyed(this)
      )
      .subscribe((regUpdate: RegistrationTransaction) => {
        //Update the user details
        
        this._profileService.updateUser(this.userDetails.id, this.userDetails).subscribe();
      });
    });

    //fresh transaction case
    this.route.queryParamMap
    .pipe(
      map(params => params.get('payment')),
      filter(param => param == null),
      tap((paymentInd: string) => {
        this.setOrderDetails();

        //this is to make sure user sees the correct template even after refresh
        this.showConfirmation = true;
        this.showRegConfirmation = false;
      }),
      filter(id => this.registrationTransactionDetails.id == null),
      untilDestroyed(this)
    )
    .subscribe((paymentInd: string) => {
      this.regService
        .createTransaction(this.registrationTransactionDetails)
        .pipe(
          filter(regCreated => regCreated.id != null),
          tap((regCreated: RegistrationTransaction) => {
            if(!this._utilities.isNullOrUndefined(regCreated.id)){

              this.wizard = new KTWizard(this.el.nativeElement, {
                startStep: 1
              });

              this.initializeWizardEvents();

              
                                            
              this.regService
              .getSessionDetails(regCreated.eventId, regCreated.order.id)
              .pipe(
                tap((response: SessionResponse) => {
                  //Just so that we can update the registration details with the gateway order id as well.
                  this.registrationTransactionDetails = JSON.parse(this.storage.getItem(Constants.RegistrationStorageKey));
                  this.registrationTransactionDetails.gatewayOrderId = response.gatewayOrderId;

                  this.storage.setItem(Constants.RegistrationStorageKey, JSON.stringify(this.registrationTransactionDetails));

                }),
                untilDestroyed(this)
                )
              .subscribe((response: SessionResponse) => {
                console.log(response);

                this.gatewayConfig.session = response.session;
                this.gatewayConfig.callbacks = this.callback;

                Checkout.configure(this.gatewayConfig);
              });
            }
          }),
          untilDestroyed(this)
          )
        .subscribe((regCreated: RegistrationTransaction) => {
          
          this.registrationTransactionDetails = JSON.parse(this.storage.getItem(Constants.RegistrationStorageKey));
          
          //Save all the details fo the registration created.
          this.registrationTransactionDetails.id = regCreated.id;
          this.registrationTransactionDetails.order.id = regCreated.order.id;
          this.registrationTransactionDetails.order.orderLine.id = regCreated.order.orderLine.id;

          this.storage.setItem(
            Constants.RegistrationStorageKey,
            JSON.stringify(this.registrationTransactionDetails)
          );
        });     
    });

    this.regService
      .getCommonValues()
      .pipe(
        filter(list => list != null),
        tap((list: CommonLists) =>{
          this.filteredCities = _.filter(list.cities, { countryCode: this.userDetails.countryCode });
        }),
        untilDestroyed(this)
        )
      .subscribe((lists: CommonLists) => {
        this.countries = lists.countries;
        this.titles = lists.titles;
        this.genders = lists.genders;
        this.cities = lists.cities;
        this.jobFunctions = lists.jobFunctions;
        this.jobTitles = lists.jobTitles;
      });

  }

ngAfterViewInit()

ngAfterViewInit(): void {
    // Initialize form wizard
    //let wizard;
    
    if(this.showRegConfirmation && !this.showConfirmation)
    {
      this.wizard = new KTWizard(this.el.nativeElement, {
        startStep: 3
      });
    }
    else
    {
      this.wizard = new KTWizard(this.el.nativeElement, {
        startStep: 2
      });
    }
    

    // Validation before going to next page
    this.wizard.on('beforeNext', wizardObj => {
      // https://angular.io/guide/forms
      // https://angular.io/guide/form-validation
      // validate the form and use below function to stop the wizard's step
      // wizardObj.stop();
      this.onBeforeNext(wizardObj);
    });

    this.wizard.on('beforePrev', wizardObj => {
      // https://angular.io/guide/forms
      // https://angular.io/guide/form-validation
      // validate the form and use below function to stop the wizard's step
      // wizardObj.stop();
      this.onBeforePrev(wizardObj);
    });

    // Change event
    this.wizard.on('change', wizard => {
      setTimeout(() => {
        KTUtil.scrollTop();
      }, 500);
    });
  }

Now this wizard has to be initialized in the ngAfterViewInit. I have tried to do it in the ngOnInit but it doesn't work. The events do not work.

Primarily the lines you all see in the queryParamsMap subscriptions are my attempt to achieve what I want to achieve. I want to start the wizard from a different step based on the state user land to the screen.

this.wizard = new KTWizard(this.el.nativeElement, {
                startStep: 2
              });
    
              this.initializeWizardEvents();

Additionally you all can find this line: this.setOrderDetails() <- it fetches the user details during the ngOnInit. It is run after the ngOnInit and hence I get some initialization errors during runtime undefined title error as pasted above. When data comes it fills the UI, but I don't quite understand how to get around with this error.

RegistrationComponent.html Portion throwing error

<!--begin: Form Wizard Step 2-->
          <div class="kt-wizard-v3__content" data-ktwizard-type="step-content" data-ktwizard-state="current">
            <div class="kt-form__section kt-form__section--first">
              <!-- <ng-template #loggedIn> -->
              <div class="kt-wizard-v3__form">
                <div class="wizard-title-area">
                  <p class="para">
                    <span class="name">Hi {{ userDetails.firstName }}, </span>You have logged in using your social
                    account. <a href="#">Click here</a> if this is not the correct information.
                  </p>
                </div>
                <div class="kt-separator kt-separator--border-2x separator-margin-top-0"></div>
                <form #userForm="ngForm">
                  <div class="form-input-wrap">
                    <div class="row">
                      <div class="col-sm-2">
                        <div class="form-group">
                          <mat-form-field>
                            <mat-select
                              id="prefix"
                              name="prefix"
                              placeholder="prefix"
                              [(ngModel)]="userDetails.titleId"
                              name="userTitle"
                              id="userTitle"
                              required
                            >
                              <mat-option *ngFor="let title of titles" [value]="title.id">{{
                                title.description
                              }}</mat-option>
                            </mat-select>
                          </mat-form-field>
                          <!-- <div
                            *ngIf="userTitle.invalid && (userTitle.dirty || userTitle.touched)"
                            class="alert alert-danger"
                          >
                            <div *ngIf="userTitle.errors.required">
                              Please select a Title
                            </div>
                          </div> -->
                        </div>
                      </div>
                      <div class="col-sm-4">
                        <div class="form-group">
                          <mat-form-field>
                            <input
                              matInput
                              #input
                              maxlength="20"
                              placeholder="First Name"
                              required
                              [(ngModel)]="userDetails.firstName"
                              [ngModelOptions]="{ standalone: true }"
                            />
                          </mat-form-field>
                        </div>
                      </div>
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <input
                              matInput
                              #input
                              maxlength="20"
                              placeholder="Last Name"
                              required
                              [(ngModel)]="userDetails.lastName"
                              [ngModelOptions]="{ standalone: true }"
                            />
                          </mat-form-field>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="form-input-wrap">
                    <div class="row">
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <input
                              matInput
                              type="email"
                              #input
                              maxlength="20"
                              placeholder="email"
                              required
                              [(ngModel)]="userDetails.email"
                              [ngModelOptions]="{ standalone: true }"
                            />
                          </mat-form-field>
                        </div>
                      </div>
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <mat-select
                              id="gender"
                              name="gender"
                              placeholder="gender"
                              [(ngModel)]="userDetails.genderId"
                              aria-required="true"
                              [ngModelOptions]="{ standalone: true }"
                            >
                              <mat-option *ngFor="let gender of genders" [value]="gender.id">{{
                                gender.alias
                              }}</mat-option>
                            </mat-select>
                          </mat-form-field>
                        </div>
                      </div>
                    </div>
                  </div>

                  <div class="form-input-wrap">
                    <div class="row">
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                              <mat-label>Select your nationality</mat-label>

                            <mat-select
                              placeholder="nationality"
                              [(ngModel)]="userDetails.nationalityCode"
                              [ngModelOptions]="{ standalone: true }"
                            >
                              <mat-option *ngFor="let country of countries" [value]="country.code">{{
                                country.name
                              }}</mat-option>
                            </mat-select>
                          </mat-form-field>
                        </div>
                      </div>
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                              <mat-label>Select the country of residence</mat-label>
                            <mat-select
                              placeholder="country"
                              [(ngModel)]="userDetails.countryCode"
                              [ngModelOptions]="{ standalone: true }"
                              (selectionChange)="onCountryChange()"
                            >
                              <mat-option *ngFor="let country of countries" [value]="country.code">{{
                                country.name
                              }}</mat-option>
                            </mat-select>
                          </mat-form-field>
                        </div>
                      </div>
                    </div>
                  </div>

                  <div class="form-input-wrap">
                    <div class="row">
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <mat-label>Select a job function</mat-label>
                            <mat-select
                              matInput
                              id="userJobFunction"
                              name="userJobFunction"
                              placeholder="job function"
                              [(ngModel)]="userDetails.jobFunctionId"
                              required
                              #userJobFunction="ngModel"
                            >
                              <mat-option *ngFor="let function of jobFunctions" [value]="function.id">{{
                                function.name
                              }}</mat-option>
                            </mat-select>
      
                            <mat-error *ngIf="userJobFunction.hasError('required')">
                                Please select a job function.
                            </mat-error>
      
                          </mat-form-field>
                        </div>
                      </div>
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <mat-label>Select a job title</mat-label>
                            <mat-select
                              matInput
                              id="userJobTitle"
                              name="userJobTitle"
                              placeholder="job title"
                              [(ngModel)]="userDetails.jobTitleId"
                              required
                              #userJobTitle="ngModel"
                            >
                              <mat-option *ngFor="let jobTitle of jobTitles" [value]="jobTitle.id">{{
                                jobTitle.name
                              }}</mat-option>
                            </mat-select>
                            <mat-error *ngIf="userJobTitle.hasError('required')">
                                Please select a job title.
                            </mat-error>
                          </mat-form-field>
                        </div>
                      </div>
                    </div>
                  </div>


                  <div class="form-input-wrap">
                    <div class="row">
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <input
                              matInput
                              type="text"
                              #input
                              maxlength="20"
                              placeholder="phone"
                              required
                              [(ngModel)]="userDetails.mobile"
                              [ngModelOptions]="{ standalone: true }"
                            />
                          </mat-form-field>
                        </div>
                      </div>
                      
                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <mat-label>Select a city</mat-label>
                            <mat-select
                              matInput
                              id="userCity"
                              name="userCity"
                              placeholder="city"
                              [(ngModel)]="userDetails.city"
                              required
                              #userCity="ngModel"
                            >
                              <mat-option *ngFor="let city of filteredCities" [value]="city.id">
                                {{ city.name }}
                              </mat-option>
                            </mat-select>
      
                            <mat-error *ngIf="userCity.hasError('required')">
                                Please select a city.
                              </mat-error>
      
                          </mat-form-field>
                        </div>
                      </div>

                    </div>
                  </div>

                  <div class="form-input-wrap">
                    <div class="row">
                      <div class="col-sm-6">
                        <div class="form-group">

                          <mat-form-field>
                            <input matInput [matDatepicker]="userDOB" [max]="maxDate" id="userDOB" name="userDOB" placeholder="Date of Birth" [(ngModel)]="userDetails.userDOB"
                            [ngModelOptions]="{ standalone: true }">
                            <mat-datepicker-toggle matSuffix [for]="userDOB"></mat-datepicker-toggle>
                            <mat-datepicker #userDOB disabled="false"></mat-datepicker>
                          </mat-form-field>
                        </div>
                      </div>

                      <div class="col-sm-6">
                        <div class="form-group">
                          <mat-form-field>
                            <input
                              matInput
                              type="text"
                              #input
                              maxlength="20"
                              placeholder="company"
                              [(ngModel)]="registrationTransactionDetails.companyName"
                              [ngModelOptions]="{ standalone: true }"
                            />
                          </mat-form-field>
                        </div>
                      </div>
                    </div>
                  </div>                  
                </form>
              </div>
              <!-- </ng-template> -->
            </div>
          </div>
          <!--end: Form Wizard Step 2-->

Any help will be appreciated. Thank you!

1

There are 1 answers

4
XxtuperdicionxX On

seens like a bad definition variable, in your template RegisterationComponent.htmlline 88, you should have something like variable.titleId, that variable in your .ts file should be define like this:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class YourComponentName {
  variable;
}

the problem is that when your component is loaded variable is undefined so you should define like an empty object variable: typing = {} so the html component will get and undefined intead a error, if you component is specting something different that undefined you sould define your variable like variable = {titleId: expectedValue}