I am making angular application with angular dynamic form where i am loading data for dynamic form through JSON..
JSON has two parts such as part 1 and part 2,
jsonDataPart1: any = [
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_name",
"label": "Project Name",
"type": "text",
"value": "",
"required": false,
"minlength": 3,
"maxlength": 20,
"order": 1
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_desc",
"label": "Project Description",
"type": "text",
"value": "",
"required": true,
"order": 2
}
]
jsonDataPart2: any = [
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "property_one",
"label": "Property One",
"type": "text",
"value": "",
"required": true,
"order": 3
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "property_two",
"label": "Property Two",
"type": "text",
"value": "",
"required": true,
"order": 4
},
{
"elementType": "radio",
"class": "col-12 col-md-3 col-sm-12",
"key": "property_required",
"label": "Property Required",
"options": [
{
"key": "required",
"value": "Required"
},
{
"key": "not_required",
"value": "Not Required"
}
],
"order": 5
},
{
"elementType": "checkbox",
"class": "col-12 col-md-3 col-sm-12",
"key": "property_check",
"label": "Property Required",
"order": 6
}
]
And i am loading the JSON as separate part like,
ngOnInit() {
//Building form first part
this.questions = this.service.getQuestions(this.jsonDataPart1)
this.form = this.qcs.toFormGroup(this.questions);
//Building form second part
this.questionsPartTwo = this.service.getQuestions(this.jsonDataPart2)
this.formPartTwo = this.qcs.toFormGroupPartTwo(this.questionsPartTwo);
}
And HTML looks like,
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<h4> <b> Form Part One begins from here </b> </h4>
<div *ngFor="let question of questions" class="form-row">
<ng-container>
<app-question [question]="question" [form]="form"></app-question>
</ng-container>
</div>
<h4> <b> Form Part Two begins from here </b> </h4>
<div *ngFor="let partTwo of questionsPartTwo">
<ng-container>
<app-question [question]="partTwo" [form]="formPartTwo"></app-question>
</ng-container>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</form> <br>
<pre>
{{form?.value|json}}
</pre>
</div>
I need to combine these two and need to get single output for the whole single form..
In my real application i am having two json data and loading each separately and assigning form so please dont break out the part one and part two json..
Let me stop the code here as its getting long you might get confused..
Here is a working stackblitz: https://stackblitz.com/edit/angular-x4a5b6-2rykpo
Just take a workaround dynamic-form.component.ts and dynamic-form.component.html You will get clear what i have done..
Kindly help me to load the splitted JSON to this.service.getQuestions and get two parts and join both in final output to submit..
If i am wrong in approach kindly help me to correct it as i am new in angular and dynamic form.. It needs to be in angular dynamic form and json loading only no change in it..
Help i am expecting is from combining both parts 1 and 2 while submitting form..
Please help me i am stucking for a long to come out of it..
Use Top level form and wrap your child form inside the parent form and use provider to use existing Form
Parent.component.html
<form [formGroup]="form" (ngSubmit)="onClick();">
<h1>Part1</h1>
<div class="row" formArrayName="part1" *ngFor="let c of form['controls']['part1']['controls'];let i =index">
<div class="col">
<input [attr.type]="jsonDataPart1[i].type"
class="form-control" [attr.placeholder]="jsonDataPart1[i].label"
[formControlName]="i" >
</div>
</div>
<app-part2 [part2]="jsonDataPart2">
<h1>Part2</h1>
</app-part2>
<br>
<button class="btn btn-primary">Save</button>
</form>
child.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { FormGroup, FormControl, ControlContainer, FormGroupDirective, FormArray } from '#angular/forms';
#Component({
selector: 'app-part2',
templateUrl: './part2.component.html',
styleUrls: ['./part2.component.css'],
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class Part2Component implements OnInit {
#Input('part2') part2;
part2Form;
constructor(private parentForm: FormGroupDirective) { }
ngOnInit() {
this.part2Form = this.parentForm.form;
const control = this.part2.map(d => new FormControl());
this.part2Form.addControl('part2F', new FormGroup({
part2: new FormArray(control)
}))
}
}
Example:https://stackblitz.com/edit/angular-xrrabj
You can take several aproach
1.-Create a formJoin that was {form1:questions of form1,form2:questions of form2}
In your ngOnInit
formJoin:FormGroup;
ngOnInit()
{
this.questions = this.service.getQuestions(this.jsonDataPart1)
this.form = this.qcs.toFormGroup(this.questions);
this.questionsPartTwo = this.service.getQuestions(this.jsonDataPart2)
this.formPartTwo = this.qcs.toFormGroup(this.questionsPartTwo);
this.formJoin=new FormGroup({form1:this.form,form2:this.formPartTwo})
}
//In your .html
<form (ngSubmit)="onSubmit()" [formGroup]="formJoin">
2.-Join the question in an unique json
this.allquestions=this.service.getQuestions(
this.jsonDataPart1.concat(this.jsonDataPart2));
this.formJoin2=this.qcs.toFormGroup(this.allquestions);
//get the first question.key in the second form
this.questionBreak=this.jsonDataPart2[0].key;
//In your .html
<form (ngSubmit)="onSubmit()" [formGroup]="formJoin2">
<h4> <b> An uniq Form </b> </h4>
<div *ngFor="let question of allquestions" class="form-row">
<!--make a break fro the secondForm-->
<ng-container *ngIf="question.key==questionBreak">
<h4> <b> Form Part Two begins from here </b> </h4>
</ng-container>
<ng-container>
<app-question [question]="question" [form]="formJoin2"></app-question>
</ng-container>
</div>
</form>
IMPORTANT NOTE: You needn't have two functions in service toFormGroup and toFormGroupPartTwo. If you see is the same function, you "feed" the function with different arguments, but is the same function.
In the stackblitz has the two options together
Update
update code to make a break,
Related
I generate dynamic forms based on a json file. But they will be displayed among each other like this:
but it should be displayed like this (this is for example 3 inputs, if I have 4 the fourth one should be next to the third one and so on.. )
<div v-for="(item, index) in json" :key="index">
<b-form-input v-if="item.type" :type="item.type"></b-form-input>
</div>
my object:
[
{
"key": "key1",
"label": "Input Type Text",
"type": "text",
"value": ""
},
{
"key": "key2",
"label": "Input Type Number",
"type": "number",
"value": ""
},
{
"key": "key3",
"label": "Input Type Number",
"type": "number",
"value": ""
}
]
I've tried to solve it with class="row", but its not working because it's still the same that they are among each other..
is it possible that it should only "col-6" my input fields. I have also some selectoin and checkboxes... these I want to have "col-12"
Two ways I would approach this, depending on circumstances
First, if you can edit your input object and want more freedom in your classes you could do this:
Method 1
Edit your object to include classes
[
{
"key": "key1",
"label": "Input Type Text",
"type": "text",
"value": "",
"classes": "col-6"
},
{
"key": "key2",
"label": "Input Type Number",
"type": "number",
"value": "",
"classes": "col-6"
},
{
"key": "key3",
"label": "Input Type Number",
"type": "number",
"value": "",
"classes": "col-6 another-class"
}
]
Then use the classes in your html
<div class="row">
<div
v-for="(item, index) in json"
:key="index"
:class="item.classes"
>
<b-form-input v-if="item.type" :type="item.type"></b-form-input>
</div>
</div>
Method 2
Assign classes with a condition based on input type
<div class="row">
<div
v-for="(item, index) in json"
:key="index"
:class="{
'col-6': item.type === 'number' || item.type === 'text' || item.type === 'email',
'col-12': item.type === 'select' || item.type === 'checkbox',
}"
>
<b-form-input v-if="item.type" :type="item.type"></b-form-input>
</div>
</div>
answer based on s4k1b's
From the tags I can see you are using bootstrap
In bootstrap you can use row and col to align items side by side in a row.
<div class="row">
<div v-for="(item, index) in json" :key="index" class="col-6">
<b-form-input v-if="item.type" :type="item.type"></b-form-input>
</div>
</div>
I am making angular dynamic form with form-elements loaded through JSON..
The form element generation are working fine, but i am in the need of change of form elements based on options selected from dropdown..
JSON that generates form-elements
jsonData: any = [
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_name",
"label": "Project Name",
"type": "text",
"value": "",
"required": false,
"minlength": 3,
"maxlength": 20,
"order": 1
},
{
"elementType": "dropdown",
"key": 'project',
"label": 'Choose option to display',
"options": [
{ "key": 'description', "value": 'Show Project Description' },
{ "key": 'level', "value": 'Show Project Level' },
{ "key": 'members', "value": 'Show Project Members' }
],
"order": 2
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_desc",
"label": "Project Description",
"type": "text",
"value": "",
"order": 3
},
{
"elementType": "dropdown",
"key": 'project_level',
"label": 'Choose Project Level',
"options": [
{ "key": 'low', "value": 'Low' },
{ "key": 'medium', "value": 'Medium' },
{ "key": 'high', "value": 'High' }
],
"order": 4
},
{
"elementType": "dropdown",
"key": 'project_members',
"label": 'Choose Project Member',
"options": [
{ "key": 'developer', "value": 'Developer' },
{ "key": 'leader', "value": 'Leader' },
{ "key": 'manager', "value": 'Manager' }
],
"order": 5
}
];
Based on the above JSON, the elements are generated..
Here you can see that Order 1 has textbox with project name which has no changes.
Then in next we have a dropdown with key as project, from here only the requirement starts..
In options, if i choose Show Project Description, then the Project Description textbox needs to be displayed and other two project_level and project_members needs to be in hidden format..
Likewise if i choose Show Project Level then the Project Level dropdown alone needs to be displayed and the Project Description and Project Members needs to be in hidden..
In the same way for Project Members..
So how to change the form-elements based on the selection of project dropdown values??
The working example for the same was here https://stackblitz.com/edit/angular-x4a5b6-5ys5hf
Kindly help me to hide the other elements based on the selection from the project dropdown using angular dynamic form alone..
As #benshabatnoam say the only thing you need is change you dinamic-form-question to include some like
<div [formGroup]="form" *ngIf="?????">
You can use a condition like #Benshabatnoam say, but I suggest you some more "parametrizable"
Imagine your json has a new property "visible" that was an object with two properties, field and value. So, your element "project_desc" can be like
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_desc",
"label": "Project Description",
"type": "text",
"value": "",
"order": 3,
"visible":{"field":"project","value":'description'} //<--add this "property"
},
So the dinamic-form-question can be like
<div [formGroup]="form"
*ngIf="!question.visible ||
form.get(question.visible.field).value==question.visible.value">
...
</div>
See that I include the condition (!question.visible) so, if you don't give the property "visible" to one field, this field is showed always.
Well, you must work some more, you must change question-base.ts to admit this new property
export class QuestionBase<T> {
value: T;
...
visible:any; //<--ad this property
constructor(options: {
value?: T,
...
visible?:any, //In constructor include "visible" too
..
}){
this.value = options.value;
...
this.visible = options.visible;
}
You can see your forked stackblitz
You need to make few changes to your code.
Change the json so that the options key will match the controls key.
...
{
"elementType": "dropdown",
"key": 'project',
"label": 'Choose option to display',
"options": [
{ "key": 'project_desc', "value": 'Show Project Description' },
{ "key": 'project_level', "value": 'Show Project Level' },
{ "key": 'project_members', "value": 'Show Project Members' }
],
"order": 2
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_desc",
"label": "Project Description",
"type": "text",
"value": "",
"order": 3
},
...
In your form add a *ngIf to the app-question component that will execute a function passing it the question, and this function will holding the logic for hiding the given question.
<app-question *ngIf="isShowQuestion(question)" [question]="question" [form]="form">
</app-question>
The function logic would check if the question is one of the controls you want to hide, and if so it will check the value of the dropdown 'option to display' for match, if match it will show the question else it will hide the question.
isShowQuestion(question: QuestionBase<any>): boolean {
// If one of the controls you want to hide
if (question.key === 'project_desc' ||
question.key === 'project_level' ||
question.key === 'project_members') {
// if the option to display have value and it is this question that show it else don't show it
return !this.form.controls['project'].value ||
this.form.controls['project'].value === question.key;
} else { // if not, always display
return true;
}
}
I've forked your stackblitz project, so you can see it in action here.
I am making form builder using angular dynamic form, where i am loading the data for form from JSON as,
jsonData: any = [
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "first_name",
"label": "First Name (Part 1 has first name and last name with title name of Person Name)",
"type": "text",
"value": "",
"required": true,
"minlength": 3,
"maxlength": 20,
"order": 1
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "last_name",
"label": "Last Name",
"type": "text",
"value": "",
"required": true,
"order": 2
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "email",
"label": "Email (Part 2 begins from here with title Personal details)",
"type": "text",
"value": "",
"required": true,
"minlength": 3,
"maxlength": 20,
"order": 3
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "mobile",
"label": "Mobile Number",
"type": "text",
"value": "",
"required": true,
"order": 4
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "age",
"label": "Age",
"type": "text",
"value": "",
"required": true,
"order": 4
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "Father Name",
"label": "Father Name (Part 3 begins from here with Family details)",
"type": "text",
"value": "",
"required": true,
"minlength": 3,
"maxlength": 20,
"order": 5
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "mother_name",
"label": "Mother Name",
"type": "text",
"value": "",
"required": true,
"order": 6
}
];
Here everything works fine in generating the complete form..
But i would like to split up the form into Person Name, Personal details, Family Details with 2, 3, 2 input boxes respectively (the count may differ and its not static)..
A clear working example https://stackblitz.com/edit/angular-x4a5b6-geesde
In this example you can see that the Json is generated as complete form and unable to make the title in between where i want..
How can i make a split up in the form and initiate title for respective part..
I would like to have order split up exactly like the below with title for each respectively.
Person Name
-> First Name
-> Last Name
Personal Details
-> Email
-> Mobile Number
-> Age
Family Details
-> Father Name
-> Mother Name
Kindly go through the demo, which has the file with JSON and kindly help me to split up the form like the above order.
I've once implemented something similar to what you are doing now. The idea is to create a special elementType for holding element groups.
The group config for person name, for example will look something like this:
const grouped: any = {
"elementType": "group",
"label": "Person Name",
"children":[
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "first_name",
"label": "First Name",
"type": "text",
"value": "",
"required": true,
"minlength": 3,
"maxlength": 20,
"order": 1
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "last_name",
"label": "Last Name",
"type": "text",
"value": "",
"required": true,
"order": 2
}
]
};
For the group you will need to create a separate component that loops through the children. You should also consider the case where there are groups inside groups. So, you need to make it recursive.
E.g.
<div *ngFor="let question of questions" class="form-row">
<ng-container *ngIf="!question.children">
<app-question [question]="question" [form]="form"></app-question>
</ng-container>
<ng-container *ngIf="question.elementType === "group" && question.children && question.children.length > 0">
<app-dynamic-group [questions]="question.children" [form]="form"></app-dynamic-group>
</ng-container>
</div>
Inside group container component you actually do something very similar to what you do in the dynamic form so, you could combine the functionality:
<div *ngFor="let question of questions" class="form-row">
<ng-container *ngIf="!question.children">
<app-question [question]="question" [form]="form"></app-question>
</ng-container>
<ng-container *ngIf="question.elementType === "group" && question.children && question.children.length > 0">
<app-dynamic-group [questions]="question.children" [form]="form"></app-dynamic-group>
</ng-container>
</div>
Let me know if you need further explanation.
Here's a working version: https://stackblitz.com/edit/angular-x4a5b6-gwkc2z?file=src%2Fapp%2Fdynamic-group.component.html
If you want to Create sub form Component
Inside your top level form component place all you sub component
app.component.html
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<app-person></app-person>
<app-family></app-family>
<app-personal></app-personal>
<button class="btn btn primary">Save</button>
</form>
Use ControlContainer
ControlContainer
A base class for directives that contain multiple registered instances
of NgControl. Only used by the forms module.
ViewProviders to provide ControlContainer and use existing formGroupDiretive to get the parentForm then addformControl
app-person.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, ControlContainer, FormGroupDirective } from '#angular/forms';
#Component({
selector: 'app-personal',
templateUrl: './personal.component.html',
styleUrls: ['./personal.component.css'],
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class PersonalComponent implements OnInit {
personalForm;
constructor(private parentForm: FormGroupDirective) { }
ngOnInit() {
this.personalForm = this.parentForm.form;
this.personalForm.addControl('personal', new FormGroup({
email: new FormControl(''),
mobile: new FormControl(''),
age: new FormControl('')
}))
}
}
Example:https://stackblitz.com/edit/angular-ewdzmp
I have a data provider in Angular 5.
export class DataProvider {
location: any;
private locations: any[] = [
{
"name": "restaurant",
"location": "downtown",
"category": "restaurant",
"id": "1"
},
{
"name": "law firm",
"location": "center city",
"category": "office",
"id": "2"
},
{
"name": "public library",
"location": "suburb",
"category": "library",
"id": "3"
} ]
and here's my HTML file.
<div *ngFor="let location of locations">
<h1></h1>
<h2></h2>
</div>
In this scenario, how can I load a specific object (item) in data array with certain attributes?
For instance, If I want to load an object with "category" :"restaurant", what should I do on my HTML file? so the two others should not show up. I just want to load one out of three by using this specific attribute.
Thanks.
You need to access the attribute. You can use the following example
<div *ngFor="let location of locations">
<h1>{{location.name}}</h1>
<h2>{{location.category}}</h2>
</div>
EDIT
To filter depending on a category:
locationsFiltered = this.locations.filter(location =>location.category=="restaurant");
Now to make this example practical I will create a method to do it
filter(category : string) : any[] {
return this.locations.filter(location =>location.category==category);
}
then in html
<div *ngFor="let location of locationsFiltered">
<h1>{{location.name}}</h1>
<h2>{{location.category}}</h2>
</div>
Using my json file, I'm making select type of questions on my web app.
The code I have for my select tag is like following:
<div class="form-group" ng-class="{ 'has-error': form.$submitted && form[field.id].$invalid }" ng-if="field.type === 'select'">
<label for="{{field.id}}">{{field.title}}</label>
<br>
<select ng-model="formData[field.id]" ng-value="{{value.title.id}}" ng-options="value as value.title for value in field.values">
<option disabled selected value> -- select an option -- </option></select>
<p class="form-group-note" ng-if="field.info" ng-bind="field.info"></p>
<div ng-show="form.$submitted" ng-cloack>
<span class="help-block" ng-show="form['{{field.id}}'].$error.required" ng-if="field.validations.required">Please enter a value, this field is required</span>
</div>
</div>
I have a json file like following:
{
"groups": [
{
"id": "10_8_group",
"title": "Existence",
"index": 60,
"part": 10,
"sections": [
{
"id": "10_8_section",
"title": "Existence",
"fields": [
{
"id": "10_8_labor_organization",
"title": "8. Does it exist?",
"info": "Always select \"No\"",
"type": "select",
"size": {
"width": 100,
"height": 1
},
"values": [
{
"id": 1,
"title": "Yes"
},
{
"id": 2,
"title": "No. If no, proceed to Part 9. and type or print your explanation."
}
]
}
]
}
]
}
]
}
when the data is being saved in localStorage(Angular's frontend model), it saves the data as "10_8_labor_organization":{"id":1,"title":"Yes"}}
However, I would like to save this as
"10_8_labor_organization":"Yes"
How can I accomplish this??
write your code like value.id as value.title for value in field.values