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!
seens like a bad definition variable, in your template
RegisterationComponent.htmlline88, you should have something likevariable.titleId, that variable in your .ts file should be define like this: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 andundefinedintead a error, if you component is specting something different thatundefinedyou sould define your variable likevariable = {titleId: expectedValue}