I have an angular Form and it used *ngFor for repeat an input box. Also, I have to validate dynamically all input boxes that are generated by *ngFor.
There is a JSON array called list that contains Questions. and I have to generate input boxes for answers and they should be validated. Validation information also in the list.
This is an example of the list
[{id:1,
mandatory:false,
questionDescription:"Blue flags are in position and contain words “Stop, Tank Car Connected” or “Stop, Men at Work”",
questionType:1,
validation:{maximum:100,minimum:0}},
{id:2,
mandatory:true,
questionDescription:"Wheels are chocked and handbrake(s) set” or “Stop, Men at Work",
questionType:2,
validation:{maxLength:10,minLength:3}}]
I tried to validate generated input boxes as described in here.
https://scotch.io/tutorials/angular-2-form-validation (see validation forms there).
So I have to give validation information for each input boxes like below.
(this example from given tutorial)
constructor(fb: FormBuilder){
this.complexForm = fb.group({
'firstName' : [null, Validators.required],
'lastName': [null, Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10)])]
})
this is the HTML for it.
<div class="form-group">
<label>First Name:</label>
<input class="form-control" type="text"[formControl]="complexForm.controls['firstName']">
</div>
<div class="form-group" >
<label>Last Name</label>
<input class="form-control" type="text" [formControl]="complexForm.controls['lastName']">
</div>
So in my case, all input boxes generate by *ngFor and I have to assign tags like firstName, lastName dynamically to those boxes.
So I used question.id as that tag and implemented control group as followed.
inspectionForm: FormGroup;
constructor(fb: FormBuilder) {
this.questionTemplate = templateService.getQuestionTemplate();
let objString: string = "{";
for (let item of this.questionTemplate.sectionList[0].questionList) {
if (item.questionType == 2) {
objString += '"' + item.id + '":null,';
}
}
objString = objString.slice(0, -1);
objString += '}';
let objJson = JSON.parse(objString);
for (let item of this.questionTemplate.sectionList[0].questionList) {
if (item.questionType == 2) {
objJson[item.id] = [null,Validators.compose([Validators.minLength(item.validation.minLength), Validators.maxLength(item.validation.maxLength)])];
}
}
this.inspectionForm = fb.group(objJson);
}
and here is my HTML code.
<form [formGroup]="inspectionForm" (ngSubmit)="submitForm(inspectionForm.value)">
<div *ngFor="let question of section.questionList" [ngSwitch]="question.questionType">
<ion-row *ngSwitchCase="QuestionType.String">
<ion-col width-80>{{question.questionDescription}}</ion-col>
<ion-col width-20>
<ion-input id="{{question.id}}" type="text" class="inspection-bottom-border" [formControl]="inspectionForm.controls['{{question.id}}']"></ion-input>
</ion-col>
</ion-row>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" [disabled]="!inspectionForm.valid">Submit</button>
</div>
</form>
The Problem is....
If I define [formControl] as [formControl]="inspectionForm.controls['{{question.id}}']"
I get an error like follows,
unhandled Promise rejection: Template parse errors:
Parser Error: Got interpolation ({{}}) where expression was expected at column 25 in [inspectionForm.controls['{{question.id}}']] in InspectionPage#36:91 ("idth-20>
<ion-input id="{{question.id}}" type="text" class="inspection-bottom-border" [ERROR ->][formControl]="inspectionForm.controls['{{question.id}}']"></ion-input>
then I searched for an answer in StackOverflow, I found someone has said don't use [] and {{}} in one time (Dynamic routerLink value from ngFor item giving error "Got interpolation ({{}}) where expression was expected")
But if I removed [] and defined as follows
formControl="inspectionForm.controls['{{question.id}}']"
I get an error as follows
EXCEPTION: Error in ./InspectionPage class InspectionPage - inline template:36:91 caused by: Cannot create property 'validator' on string 'inspectionForm.controls['2']'
(this '2' the is question.id of first type 2 question)
if I define as follows same error occurs
formControl="inspectionForm.controls['2']"
But the problem is if I define as follows with [formControl]
[formControl]="inspectionForm.controls['2']"
The app is Working Perfectly....
But I have to give question.id dynamically. How I can do that?
Are there someone have a solution for this error. Please give a suggestion.
Thank you very much.
just use [formControl]="inspectionForm.controls[question.id]"
Thanks #Sasxa for the answer
Related
I want to display data in red colour when email is invalid.
I have n no. of data, out of them some email ids are not validated.
I have used only dynamic class.
//ts file
email_regx = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/;
if (this.data.email_id) {
this.email_regx = true;
} else {
this.email_regx = false;
}
//html file
<span [ngClass]="{'redClass': email_regx}">{{ data?.email_id }}</span>
//css
.redClass{ color: red}
At first, please think about using an input field instead.
I would recommend to use FormBuilder from Angular Forms.
It will help you with a lean template and make validation a lot easier.
Could look like:
// TS
contactForm: FormGroup;
constructor() {
this.contactForm = this.formBuilder.group({
email: [
'',
Validators.compose([
Validators.pattern('^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*#[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$'),
Validators.required,
])
]
});
The FormGroup called "contactForm" here holds one input field called "email".
The validation is for your email.
// Template
<form id="contactForm" [formGroup]="contactForm">
<ion-item lines="none" class="list-item">
<div>
<ion-label position="stacked"
[class.error]="contactForm.controls.email.value != undefined && !contactForm.controls.email.valid">
E-Mail *
</ion-label>
<ion-input id="email" type="email" formControlName="email"></ion-input>
</div>
</ion-item>
</form>
The important part here is the formControlName and the [formGroup] to connect to your ts´s validation.
The part [class.error] adds the class "error" to the label. You also could use that for your input field.
You are not using regex the right way. Have a look to this doc.
You can make a simple function to test your email and return a boolean.
Component ts file :
public isValidEmail(email: string): boolean {
return /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email);
}
html file :
<span [ngClass]="{'redClass': isValidEmail(data?.email_id)}">{{ data?.email_id }}</span>
I'm using Angular5 and would like to create a form where user can insert new records with a button. Each record has many controls in it and should be changed independently from other records. I have done this many times but now I'm getting weird results.
<form #newRequestForm="ngForm">
<a class="list-group-item" *ngFor="let detail of currentRequest.details; let index$ = index">
.
.
.
<ng-select [items]="products" [searchFn]="searchProduct" (change)="productChanged($event, detail)">
<ng-template ng-label-tmp let-item="item">
{{item.code}} - {{item.name1}}
</ng-template>
</ng-select>
<input class="form-control" name="productname1" type="text" [ngModel]="detail.product.name1" />
<input class="form-control" name="productname2" type="text" [ngModel]="detail.product.name2" />
<input class="form-control" name="productname3" type="text" [ngModel]="detail.product.name3" />
<input class="form-control" type="text" name="description" [(ngModel)]="detail.description" />
The problem is that the method called by ng-select onchange productChanged sets the product names of the current selected product. (Products has 3 seperate name fields in erp.) And when this happens all 3 name fields of all records in the form changes to the name fields of the currently selected product. No matter which ng-select I use, all has changed. All the other fields working seperately eg: description.
So I suppose the bug is in the method but it looks like this:
productChanged($event, detail) {
detail.product = $event;
console.log('-----------------------------------------');
this.currentRequest.details.forEach((d, i) => {
console.log(i, d.product !== null ? d.product.name1 : '');
});
}
Yes, I have tried to debug with the good old console.log and it says that the content of each of the detail.products are perfect, according to the last selection of that record's ng-select.
The input controls are still rewritten by whatever select I make. Why?
Because you didn't specify a track by function, so Angular doesn't really know how to keep track of your inputs.
<a class="list-group-item" *ngFor="let detail of currentRequest.details; let index$ = index; trackBy: customTB">
customTB(index, item) { return index + '-' + item.product.name1; }
I've got the following 'triple level' nested form:
FormGroup->ArrayOfFormGroups->FormGroup
Top level (myForm):
this.fb.group({
name: '',
description: '',
questions: this.fb.array([])
});
Nested form array element for 'questions':
this.fb.group({
priority: ['1'],
params: this.fb.group({parameter: ['']})
});
Nested form group element for 'params' is a key:value object of random length.
I'm using the following ngFor to go through elements:
<tr *ngFor="let questionConfigForm of myForm.controls.questions.controls; let i=index" [formGroupName]="i">
...
<div *ngFor="let param of objectKeys(questionConfigForm.controls.params.controls)" formGroupName="params">
<input type="text" [formControlName]="param">
I've got the following behavior:
When I'm updating any of the fields on first two form levels I could instantly see changes in corresponding form controls values with {{myForm.value | json}}.
But if I input something in one of 'params' controls I couldn't see any changes in myForm values, but the form data for 'params' controls will be updated if I will make any changes in corresponding 'questions' form.
For me it looks like 'param' form control receives input data, but doesn't trigger some update event, and I don't know how to fix that, except writing my own function to react on (change) and patchValue in form..
So my question is how to make 'params' controls update myForm without that strange behavior?
UPD:
initQuestionConfig() {
return this.fb.group({
priority: ['1'],
params: this.fb.group({parameter: ['']}),
});
}
addQuestionConfig() {
const control = <FormArray>this.myForm.controls['questions'];
const newQuestionCfg = this.initQuestionConfig();
control.push(newQuestionCfg);
}
Finally the problem is solved.
The root of this issue was the way I've cleaned up already existing 'params'.
To remove all parameters from 'questions' I used the following code:
const control = <FormArray>this.myForm.controls['questions'];
control.controls[index]['controls'].params = this.fb.group([]);
And the reason of those glitches was this new 'fb.group' instance.
Now I'm removing params one by one, keeping original formGroup instance and it works as expected:
const control = <FormArray>this.myForm.controls['questions'];
const paramNames = Object.keys(control.controls[index]['controls'].params.controls);
for (let i = 0; i < paramNames.length; i++) {
control.controls[index]['controls'].params.removeControl(paramNames[i]);
}
#MilanRaval thanks for your time again :)
Try this: Give formArrayName and formGroupName like below...
<div formArrayName="testGroup">
<div *ngFor="let test of testGroup.controls; let i=index">
<div [formGroupName]="i">
<div class="well well-sm">
<label>
<input type="checkbox" formControlName="controlName" />
</div>
</div>
</div>
</div>
I'm making a question editor and I have a page, where current question and its' answers are printed. There are 3 types of questions, so, I need different fields for different types.
On editor's page there is a Select (for type changing) and then there is ion-list with answers. Each answer is an ion-item with a label with ng-if (tried ng-show too), based on selected question type. Types are got from mySQL DB firstly.
Problem is, the page after init shows all fields correctly, but when I change the type of question, all I see is fields without ng-if. I tried to reload the view, setting ng-change on Select (function with $state.reload), but it doesn't help.
And now the code:
Select:
<select
ng-model="question.type" ng-options="type.id for type in types"
ng-change="onTypeChange(question)">
</select>
One of the ion-items:
<ion-list>
<ion-item ng-repeat="answer in answers">
<label ng-if="question.type == 1 || question.type == 4"
class="item item-input item-stacked-label">
<span class="input-label">Ответ:</span>
<input type="text" placeholder={{answer.answer}} ng- model="answer.answer">
</label>
</ion-item>
</ion-list>
onTypeChange():
$scope.onTypeChange = function(question){
var typeUpdateQuery = 'UPDATE `questions` SET "type" = "' + question.type.id + '" WHERE "id" = ' + question.id;
$cordovaSQLite.execute(db, typeUpdateQuery).then(
function(){
$scope.answers=[];
$scope.openQuestion();
$state.reload();
}, function (err){
error(err);
}
);
}
openQuestion preloads the view's data just taking it from DB (fills in the answers[]), nothing special.
So, what should I do?
I'm trying to set a dynamic form in Angular2.
So, in my ngOnInit function, I made a Ajax request to get a JSON with form data.
Like this :
export class CustomerEditComponent{
private customer : Customer = new Customer();
private customerForm;
constructor(private _CustomersService: CustomersService, private _routeParams: RouteParams, private _fb: FormBuilder){
this.customerForm = _fb.group({
name: [],
job: [],
arrival_date: []
});
}
ngOnInit(){
let id = this._routeParams.get('id');
this._CustomersService.getById(id).subscribe(res => {
this.customer = res;
});
}
onSubmit(event){
console.log(event);
}
}
So, at the component construct, 'customer' is equals to a newest one. (all properties are empty). But just after, we set value to every properties.
No problem for that, my input has the correct values.
But, if I submit my form, the form value is equals to :
Object {name: null, job: null, arrival_date: null}
(But the form in the view is correctly populate).
Here my form (condensed) :
<form [ngFormModel]="customerForm" (ngSubmit)="onSubmit(customerForm.value)">
<input md-input [(value)]="customer.name">
<input md-input [(value)]="customer.job">
<input md-input type="date" [(value)]="customer.arrival_date">
<button type="submit">Save</button>
</form>
I use [(value)] cause ng2-material package. (I already try with ngControl).
I think my code is 'wrong' about this feature, but I dunno where.
Thanks !
EDIT :
I have found the answer !
With ng2-material, we need to set [(value)] and [(ngModel)] together on every input like this :
<form [ngFormModel]="customerForm" (ngSubmit)="onSubmit(customerForm)">
<input md-input [(value)]="customer.name" [(ngModel)]="customer.name">
<input md-input [(value)]="customer.job" [(ngModel)]="customer.job">
<input md-input type="date" [(value)]="customer.arrival_date" [(ngModel)]="customer.arrival_date">
<button type="submit">Save</button>
</form>
[(value)] is used by ng2-material to set the value 'on front'.
I think that the problem is that you didn't associate your form inputs with their controllers within the ngFormControl directive in your template. You should refactor that way:
<form [ngFormModel]="customerForm" (ngSubmit)="onSubmit(customerForm.value)">
<input md-input [(value)]="customer.name"
ngFormControl="name">
<input md-input [(value)]="customer.job"
ngFormControl="job">
<input md-input type="date" [(value)]="customer.arrival_date"
ngFormControl="arrival_date">
<button type="submit">Save</button>
</form>
See this link from ng2-material samples: https://github.com/justindujardin/ng2-material/blob/master/examples/components/input/form_builder.html
Otherwise why don't you use the customer object instead of the customerForm.value one?
Hope it helps you,
Thierry