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. Click here 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.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}
Related
I am new to angular. I am dynamically rendering some fields into my reactive form. Everything works great when I am using ng serve with a mock request (i.e. rendering happens properly, no error in the console.log). As soon as I build the project with ng build and use a proper backend, I get the error for each field I am rendering dynamically:
main.js:1 ERROR TypeError: Cannot read property '_rawValidators' of null
I couldn't find any background on this error. I would love to hear your thoughts.
more background
// these fields change with selection
this.datafields = [{
dfId: 48,
dfName: "Phone",
dfType: "text",
dfOptions: null,
dfValue: ""
},
{
dfId: 49,
dfName: "Eval",
dfType: "select",
dfOptions: ["","Remote","Live"],
df_value: "",
}]
typescript rendering in ngOnInit (tried ngAfterViewInit with no improvement)
dfGroup = new FormGroup({})
...
...
this.eyeForm = this.formBuilder.group({
focus: ['', Validators.required],
datafields: this.formBuilder.array([])
})
...
...
if (this.datafields != null || this.datafields != undefined) {
this.datafields.forEach((x:any) => {
this.dfGroup.setControl(x.dfName, new FormControl(x.dfValue));
});
this.getDataFields.push(this.dfGroup);
}
and HTML looks like the following:
<div [formGroup]="dfGroup">
<div class="row pt-2" *ngFor="let field of datafields; let i=index">
<div class="col-4 d-flex align-items-center 13required">
{{field.dfName}}
</div>
<div class="col-6">
<mat-form-field *ngIf="field.dfType == 'text'" appearance="outline">
<input
matInput
[type]="field.dfType"
[formControlName]="field.dfName"
required
/>
</mat-form-field>
<mat-form-field
*ngIf="field.dfType == 'select'"
appearance="outline"
>
<mat-select [formControlName]="field.dfName" placeholder="">
<mat-option
[value]="option"
*ngFor="let option of field.dfOptions"
>
{{ option }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
I ran in to this situation when I was mocking out my template and typo'd my formControlName attribute.
<mycomponent formControlName="bogusfieldSpelledWrong" ...>
Why Angular showed it as this error likely has something to do with how the component was initializing/changing the form.
it must be about formControlName. check out your form control names in ts file and html file.
for nested groups you can use formGroupName, then use formControlName.
this.yourForm = this.fb.group({
name: [''],
contact: this.fb.group({
address: ['']
})
})
<form [formGroup]="yourForm">
<input type="text" formControlName="name" />
<div formGroupName="contact">
<input type="text" formControlName="address" />
</div>
</form>
This is caused by malforming the form in the view.
It was caused by misordering formArrayName, the loop and formGroupName
The form (controller)
form = <FormGroup>this.fb.group({
hops: this.fb.array([
this.fb.group({...}),
...
])
});
The view (mistake)
<li class="asset" *ngFor="let control of hops.controls; let i = index;">
<div formArrayName="hops">
<div [formGroupName]="i">
The view (corrected)
<div formArrayName="hops">
<li class="asset" *ngFor="let control of hops.controls; let i = index;">
<div [formGroupName]="i">
In our application, we had a minifier that renamed class variable names (in this case, the name of form controls) as an optimisation. This made the issue only appear in our production environments (optimisations weren't performed during local development).
The solution was to directly reference the control instance instead of using a string, so that the variable referenced in the angular template was renamed consistently with the class local variable.
Before:
<div formGroupName="myInputGroup">
<div formControlName="myInput">...</div>
</div>
After:
<div [formGroup]="myInputGroup">
<div [formControl]="myInputGroup.controls.myInput">...</div>
</div>
another reason for this issue is when you forget to assign formGroup
wrong:
export class MyComponent{
formGroup: FormGroup;
...
}
correct:
export class MyComponent{
formGroup = new FormGroup({...});
...
}
I had a similar issue, you need to add an extra div with property [formGroupName]="i",
The final code would be,
<div [formGroup]="dfGroup">
<div class="row pt-2" *ngFor="let field of datafields; let i=index">
<div [formGroupName]="i">
<div class="col-4 d-flex align-items-center 13required">
{{field.dfName}}
</div>
<div class="col-6">
<mat-form-field *ngIf="field.dfType == 'text'" appearance="outline">
<input matInput [type]="field.dfType" [formControlName]="field.dfName" required />
</mat-form-field>
<mat-form-field *ngIf="field.dfType == 'select'" appearance="outline">
<mat-select [formControlName]="field.dfName" placeholder="">
<mat-option [value]="option" *ngFor="let option of field.dfOptions">
{{ option }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
</div>
This is resolved by adding formcontrolname value.
Type error gives you null because it didn't find value from your Component.
In your Html file formcontrolname must be the same as FormGroup value.
For example:
<formControlName = 'username'>
this.formGroupName = this.formBuilder.group({
username: ["", [Validators.required]],
})
})
//html page properties
formControlName="drugReproductive"
//ts file attributes
this.drugTypeTwo = new FormControl(0);
this.drugTypeThree = new FormControl(0);
this.drugTypeFour = new FormControl(0);
this.drugTypeFive = new FormControl(0);
this.drugWithCanisterForm = this.fb.group({
drugAntineoplastic:this.drugAntineoplastic,
drugNonAntineoplastic:this.drugNonAntineoplastic,
drugReproductive:this.drugReproductive,
drugTypeOne:this.drugTypeOne,
drugTypeTwo:this.drugTypeTwo,
drugTypeThree:this.drugTypeThree,
drugTypeFour:this.drugTypeFour,
drugTypeFive:this.drugTypeFive,
configuration: this.configuration,
deviceId:this.deviceId,
deviceTypeId: this.deviceTypeId,
isOtcExcluded: this.isOtcExcluded,
isScheduleExcluded: this.isScheduleExcluded,
isUnitOfUsageExcluded: this.isUnitOfUsageExcluded,
unitOfUsageValue: this.unitOfUsageValue,
canisterQuantity: this.canisterQuantity,
canisterSize: this.canisterSize,
searchText: this.searchText,
scheduleExcludedValue: this.scheduleExcludedValue,
nioshValue:this.nioshValue,
superCellNo:this.superCellNo,
lockingCellNo:this.lockingCellNo,
superLockingCellNo:this.superLockingCellNo,
isNioshExcluded:this.isNioshExcluded
});
In my case this problem arised, because i didn't bind the formControllName attributes in my form object
I have an http request that pulls that data and I am populating this data on the UI. Now when the user clicks edit a form will show . How do we assign and show the value on the fields when the edit button was clicked ? cause I wanna use that data to submit. The email , firstname etc should display on the field.
When I click edit the value of the form field should be the value from the getuserdetails like firstname , lastname etc like the sample below {{this.data.emailAddress}} but instead I want it to be value of the input field
#init
ngOnInit(): void {
this._activatedRoute.queryParams.subscribe((params) => {
this.userId = params.id;
this.getUserGeneralDetails();
});
}
#constructor
constructor(
private _activatedRoute: ActivatedRoute,
private _notificationService: NotificationService,
private _userProfileService: UserProfileService,
private _router: Router,
private fb: FormBuilder
) {
this.generalForm.disable();
this.securityForm.disable();
}
I only wanna show the data on the formfield when Edit is clicked.
generalForm = new FormGroup({
email: new FormControl(),
fname: new FormControl(),
lname: new FormControl(),
phone: new FormControl(),
companyName: new FormControl(),
title: new FormControl(),
});
Just new to angular guys badly wanted to know techniques and ideas. Thank you.
#template
<div class="card" #generalCard>
<span class="anchor" id="general"></span>
<div class="card-header">
<mat-icon class="card-icon">group</mat-icon>
<div class="title">GENERAL</div>
<div class="spacer"></div>
<button mat-button *ngIf="generalForm.disabled" (click)="generalForm.enable()">
<mat-icon>edit</mat-icon> Edit
</button>
<button mat-button *ngIf="generalForm.enabled" (click)="generalForm.disable()">
Cancel
</button>
<button mat-stroked-button *ngIf="generalForm.enabled" (click)="saveGeneralFormChanges()">
Save Changes
</button>
</div>
<div class="card-content" *ngIf="generalForm.disabled" >
<div class="line-item">
<div class="title">EMAIL</div>
<div class="detail">{{this.data.emailAddress}}</div>
<mat-icon class="active" style="color:#00B0DB;">check_circle</mat-icon>
</div>
<div class="line-item">
<div class="title">EMAIL</div>
<div class="detail">{{this.data.emailAddress}}</div>
<mat-icon>check_circle_outline</mat-icon>
<button mat-button class="detail active">Resend welcome email</button>
</div>
<div class="line-item">
<div class="title">FIRST NAME</div>
<div class="detail">{{this.data.firstName}}</div>
</div>
<div class="line-item">
<div class="title">LAST NAME</div>
<div class="detail">{{this.data.lastName}}</div>
</div>
<div class="line-item">
<div class="title">PHONE NUMBER</div>
<div class="detail">+1 {{this.data.phoneNumber}}</div>
</div>
<div class="line-item">
<div class="title">COMPANY NAME</div>
<div class="detail">{{this.data.companyName || 'None'}}</div>
</div>
<div class="line-item">
<div class="title">TITLE</div>
<div class="detail">{{this.data.title || 'None'}}</div>
</div>
</div>
#GetUserData
getUserGeneralDetails() {
this.isInProgress = true;
this._userProfileService
.getUserGeneralDetails(this.userId)
.pipe(finalize(() => (this.isInProgress = false)))
.subscribe({
next: (res) => {
if (res.isSuccess) {
this.data = res.data;
}
},
error: (err) => this._notificationService.showError(err),
complete: noop,
});
}
#field code
<div class="card-content" *ngIf="generalForm.enabled">
<form [formGroup]="generalForm" class="generalForm">
<mat-form-field appearance="fill" class="email">
<mat-label>Email</mat-label>
<input matInput formControlName="email" />
</mat-form-field>
<button mat-raised-button class="validateEmail">Validate email</button>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="fname">
<mat-label>First Name</mat-label>
<input matInput formControlName="fname" />
</mat-form-field>
<mat-form-field appearance="fill" class="lname">
<mat-label>Last Name</mat-label>
<input matInput formControlName="lname" />
</mat-form-field>
<mat-form-field appearance="fill" class="phone">
<mat-label>Phone Number</mat-label>
<input matInput formControlName="phone" />
</mat-form-field>
<mat-form-field appearance="fill" class="companyName">
<mat-label>Company Name</mat-label>
<input matInput formControlName="companyName" />
</mat-form-field>
<mat-form-field appearance="fill" class="title">
<mat-label>Title</mat-label>
<input matInput formControlName="title" />
</mat-form-field>
</form>
</div>
</div>
Your HTML code looks like good and as per ur question you are facing issue in filling data into form controls. That can be done as follow:
constructor(private fb: FormBuilder) { }
fillUpData() {
this.generalForm = this.fb.group({
email: [this.data.email, [Validators.required]],
fname: [this.data.fname, [Validators.required]],
lname: [this.data.lname, [Validators.required]],
phone: this.data.phone,
companyName: this.data.companyName,
title: this.data.title
});
}
You need to call this method once you have data. So after your API call.
getUserGeneralDetails() {
this.isInProgress = true;
this._userProfileService
.getUserGeneralDetails(this.userId)
.pipe(finalize(() => (this.isInProgress = false)))
.subscribe({
next: (res) => {
if (res.isSuccess) {
this.data = res.data; // After this call function.
this.fillUpData();
}
},
error: (err) => this._notificationService.showError(err),
complete: noop,
});
}
After doing this if you get an error related to the form group then add condition like *ngIf="generalForm" in your HTML code... That means if form exist then render its formControl...
I have a form whose field names and required validators will change based on the form type (content_type) selected. On selecting the form_type, I get a list of required_fields and optional_fields, accordingly, I need to add Validators.required, so i can disable the submit button if the form is not filled properly.
After adding logic, i get following errors:
this.form._updateTreeValidity is not a function
and
this.form.get is not a function
and the form ends up looking empty with the following errors. If i just keep fields without any formControlName or validations it looks perfectly fine.
Code
Sample form fields
formFields = {
required_fields: {
actual_content: "Content",
name: "Name",
contact: "Phone Number"
},
optional_fields: {
additional_content: "Additional card content",
website: "Website URL",
}
};
form.component.ts
formGroup: FormGroup;
ngOnInit() {
this.selectedType = 'text';
this.service.getFormFields(this.selectedType)
.subscribe((data: {required_fields, optional_fields }) => {
// create array of field names to display in template
this.fieldNames = Object.assign(data.required_fields, data.optional_fields);
// create dynamic formGroup, with proper validators
this.formGroup = this.createGroup(data);
console.log({FormGroup: this.formGroup});
});
}
createGroup(formFields): FormGroup {
const group: any = {};
// required fields
const reqFields = Object.keys(formFields.required_fields);
reqFields.forEach(field => {
group[field] = new FormControl(``, [Validators.required]);
});
// optional fields
const optFields = Object.keys(formFields.optional_fields);
optFields.forEach(field => {
group[field] = new FormControl(``);
});
return group;
}
// the dynamically generated formGroup might look like
// formGroup = new FormGroup({
// actual_content: new FormControl(``, [Validators.required]),
// name: new FormControl(``, [Validators.required]),
// contact: new FormControl(``, [Validators.required]),
// additional_content: new FormControl(),
// website: new FormControl(),
// })
form.component.html
<form novalidate class="mainForm" [style.fontSize.px]="15" [formGroup]="formGroup" #myForm="ngForm">
<div>
<h3 class="sHeading"> Select Form Type </h3>
<mat-form-field appearance="outline" class="formField">
<mat-select placeholder="Select Form Type" formControlName="content_type">
<mat-option
#formType
class="option"
*ngFor="let type of formTypes"
[value]="type.type"
(click)="updateFormType(formType.value)">
{{type.type}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div>
<h3 class="sHeading"> {{fieldNames.name}} </h3>
<mat-form-field appearance="outline" class="formField">
<input matInput
placeholder="Name should have 3 characters min."
formControlName="name">
</mat-form-field>
</div>
<div>
<h3 class="sHeading"> {{fieldNames.content_url}} </h3>
<mat-form-field appearance="outline" class="formField">
<input matInput
placeholder="Provide Valid URL"
formControlName="content_url">
</mat-form-field>
</div>
<div>
<h3 class="sHeading"> {{fieldNames.actual_content}} </h3>
<mat-form-field appearance="outline" class="formField">
<textarea
matInput
placeholder="Describe the content"
rows=6
formControlName="actual_content"></textarea>
</mat-form-field>
</div>
<div>
<h3 class="sHeading"> {{fieldNames.additional_content}} </h3>
<mat-form-field appearance="outline" class="formField">
<textarea
matInput
placeholder="Describe the additional content"
rows=6
formControlName="additional_content"></textarea>
</mat-form-field>
</div>
<div>
<button mat-raised-button type="submit" class="submitBtn" [disabled]="!myForm.valid">Submit</button>
</div>
</form>
Question
What is the correct way to add validationss dynamically?
Thanks in advance.
I have a form (a material dialog modal) which lets the user to create an account, once the user clicks on Register button then the user stays on the same page with the username of that account that he created. The issue I am having is its getting created but its not getting updated in sync with the closing of dialog modal. Means the user should be registered and in parallel that created user value should be populated in the dropdown for better user expereince. I dont want the user to distract from the current page because if the user have entered the details and if that page is reloaded then all the data whatever he entered will be lost. So i dont want that to happen. So for that i created this below code.
<section class="container-fluid with-maxwidth chapter">
<article class="article">
<div class="box box-default">
<div class="box-body">
<mat-horizontal-stepper [linear]="true" #stepper>
<mat-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Project Registration</ng-template>
<div class="form-group row"></div>
<div class="form-group row">
<label for="name" class="col-md-0 control-label"></label>
<div class="col-md-5">
<mat-input-container class="full-width">
<input required [(ngModel)]="project.name" formControlName="nameCtrl" id="name"
matInput placeholder="Project name">
</mat-input-container>
</div>
</div>
<!-- Repository details -->
<div class="form-group row">
<label for="name" class="col-md-0 control-label"></label>
<div class="col-md-5">
<mat-input-container class="full-width">
<input required [(ngModel)]="project.url" formControlName="urlCtrl" id="repo"
matInput placeholder="Repository URL">
</mat-input-container>
</div>
<div class="col-md-7">
<div class="callout1 text-muted callout-info1">
<p>e.g. https://github.com/username/MyApp.git or
git#github.com:username/MyApp.git.
</p>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-0 control-label">
</label>
<div class="col-md-5">
<mat-form-field>
<mat-select placeholder="Repository Credentials" required
[(ngModel)]="project.account.id" formControlName="typeCtrl">
<mat-option *ngFor="let account of accounts" [value]="account.id">
{{account.name}} ({{account.accountType}})
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-md-7">
<div class="callout1 text-muted callout-info1">
<p>
Add credentials
<br/>
Credentials are required for automatic test creation and check-in.
</p>
</div>
</div>
</div>
<div>
<mat-card-actions>
<button mat-raised-button color="primary"
class="nav-btn"
[disabled]="firstFormGroup.invalid"
(click)="save(stepper);">Save & Next
</button>
</mat-card-actions>
</div>
<div class="form-group row">
<div class="col-md-0"></div>
<div class="col-md-8">
<button [disabled]="!heroForm.valid" mat-raised-button color="primary" (click)="create();"
class="btn-w-md no-margin-left">Register
</button>
<button mat-button type="button" color="primary" class="btn-w-md" (click)="onClose();">Cancel
</button>
</div>
</div>
</form>
</mat-step>
</mat-horizontal-stepper>
</div>
</div>
</article>
</section>
From the template's below code, here if go to other page and then come to here then the username is adding to the drop down properly. But i dont want the user to go back and forth to see whether his credentials are registered or not but instead i want to show the credentials created on the fly so that he can further move.
<mat-form-field>
<mat-select placeholder="Repository Credentials" required
[(ngModel)]="project.account.id" formControlName="typeCtrl">
<mat-option *ngFor="let account of accounts" [value]="account.id">
{{account.name}} ({{account.accountType}})
</mat-option>
</mat-select>
</mat-form-field>
The code for register dialog box component
export class RegisterComponent implements OnInit{
#Input() entry: Account = new Account();
create(entry: Account) {
this.handler.activateLoader();
this.snackbarService.openSnackBar(this.entry.name + " registering...", "");
this.accountService.create(entry).subscribe(results => {
this.entry = entry;
this.handler.hideLoader();
if (this.handler.handle(results)) {
return;
}
this.snackbarService.openSnackBar(this.entry.name + " registered successfully", "");
this.onClose();
// this.router.navigateByUrl('/app/projects', {skipLocationChange: true}).then(()=>
// this.router.navigate(['/app/projects/new']));
this.router.navigate(['/app/projects/new']);
}, error => {
this.handler.hideLoader();
this.handler.error(error);
});
}
onClose(){
this.dialogRef.close();
}
}
This is where i have written the service:
import { Account } from './../models/project.model';
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
import { Headers } from '#angular/http';
import { Observable, Subject } from 'rxjs';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class AccountService {
private serviceUrl = '/api/v1/accounts';
private entry = new Subject<Object>();
entry$ = this.entry.asObservable();
constructor(private http: HttpClient) {
}
create(entry: Account): Observable<any> {
return this.http.post(this.serviceUrl, entry);
}
emitAccount(entry){
this.entry.next();
}
}
I want the user to be on the same page and should update the dropdown with latest results. How is that possible? Could you please suggest me? What i have done wrong with my code.
Thanks.
create Service:
let newEntry = this.http.post(this.serviceUrl, entry);
emitAccount(newEntry);
return newEntry;
Register Component:
entry$.subscribe(updateAccount => {
accounts.push(updateAccount)
}
So to check .. it's about this section?
<mat-option *ngFor="let account of accounts" [value]="account.id">
{{account.name}} ({{account.accountType}})
</mat-option>
BEcause I don't see anything in your code repopulating accounts array after registering.
I can't see where you bind the accounts array.
I would like get values from 2 inputs (NUM_PRECIO_UNITARIO and NUM_PORCENTAJE_IVA) and show diamically the result of multplication in other input (NUM_VALOR_IVA) actually this is my html
file.html
<div class="form-group col-sm-5">
<mat-form-field>
<input matInput type="number" formControlName="NUM_VALOR_IVA" placeholder="Valor IVA" value="{{formDetalleOC.get('NUM_PRECIO_UNITARIO').value * formDetalleOC.get('NUM_PORCENTAJE_IVA').value}}" required>
</mat-form-field>
</div>
file.ts
ngOnInit() {
this.createControlItemOC
}
createControlItemOC() {
this.formDetalleOC = this.fb.group({
NUM_PRECIO_UNITARIO: 0,
NUM_PORCENTAJE_IVA: 0,
NUM_VALOR_IVA: 0,
})
}
this is the result in the front
but in my form the value not change
Create in function to multiply those values in your component's class
calcResult() {
const o = this.formDetalleOC.get('NUM_PRECIO_UNITARIO').value * this.formDetalleOC.get('NUM_PORCENTAJE_IVA').value;
this.formDetalleOC.patchValue({ NUM_VALOR_IVA: o });
return o;
}
In the HTML template, call that function:
<div class="form-group col-sm-5">
<mat-form-field>
<input matInput type="number" formControlName="NUM_VALOR_IVA" placeholder="Valor IVA" [value]="calcResult()" required>
</mat-form-field>
</div>
PS: don't use ngModel for your form inputs if you are using FormBuilder (you can use it for other things on the same page).