How to correctly use formGroup with nested data in angular reactive forms - formarray

I am new to angular and reactive forms.
I have a table with some form elements and I am trying to use reactive forms to save the data.
I was able to loop and render nested behavior of the json but unable to bind properly the form array and its controls.
The formControlName is incorrectly looping and being wrongly applied to checkbox and input text.
app.component.ts
this.userData.forEach((item)=>{
item.info.forEach((info)=> {
const temp = new FormGroup({
'active': new FormControl(info.active),
'remarks': new FormControl(info.remarks),
'id': new FormControl(item.id),
'name': new FormControl(info.name),
'number': new FormControl(info.number),
'group': new FormControl(item.group)
});
(<FormArray>this.userForm.controls['userDetails']).push(temp);
});
});
app.component.html
<div>
<h3>My Form</h3>
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<table class="table">
<thead>
</thead>
<tbody formArrayName="userDetails">
<ng-container *ngFor="let item of userData; let i = index" formGroupName="{{ i }}">
<tr>
<td colspan="5">
{{item.group}}
</td>
</tr>
<tr *ngFor="let info of item.info">
<td>
<input type="checkbox" formControlName="active">
</td>
<td>
{{info.id}}
</td>
<td>
{{info.name}}
</td>
<td>
{{info.number}}
</td>
<td>
<input type="text" formControlName="remarks">
</td>
</tr>
</ng-container>
</tbody>
</table>
<div>
<button class="btn btn-primary" type="submit">Save</button>
</div>
</form>
</div>
data
userData = [
{
"id": 123,
"group": "A Group",
"info": [
{
'name': "John Smith",
'number': "789345612",
'remarks': "Attended Last Session",
'active': true
},
{
'name': "Bob Mathers",
'number': "987345120",
'remarks': "",
'active': false
},
{
'name': "Steve Kieth",
'number': "707549120",
'remarks': "",
'active': false
}
]
},
{
"id": 456,
"group": "B Group",
"info": [
{
'name': "Mia Anne P",
'number': "880345009",
'remarks': "",
'active': false
},
{
'name': "Mathew C Brady",
'number': "7086183092",
'remarks': "No Show",
'active': false
}
]
},
{
"id": 789,
"group": "C Group",
"info": [
{
'name': "Stanley Jones",
'number': "961096478",
'remarks': "",
'active': false
},
{
'name': "Gina Qazyt",
'number': "767654730",
'remarks': "Arrived Late",
'active': false
}
]
}
]
As you can see in the below screenshot, the json data active and remarks field don't match with that of the form
I've tried placing
formGroupName="{{ i }}" and i = index
in the second loop, but then the formControlName="active" applies to every first row of the group

Do not know whether this is actually your requirement. Based on your output, I arranged the following FormGroup. This is how I would do this. Would like to see other's answers as well.
app.component.ts
userForm: FormGroup = new FormGroup({ userGroups: new FormArray([]) });
---
this.userData.forEach(
(item) => {
const userGroup = new FormGroup({
id: new FormControl(item.id),
group: new FormControl(item.group),
userDetails: new FormArray([])
});
item.info.forEach((info) => {
const userDetail = new FormGroup({
name: new FormControl(info.name),
number: new FormControl(info.number),
remarks: new FormControl(info.remarks),
active: new FormControl(info.active)
});
(userGroup.controls.userDetails as FormArray).push(userDetail);
});
(this.userForm.controls.userGroups as FormArray).push(userGroup);
}
);
app.component.html
<div>
<h3>My Form</h3>
<form [formGroup]="userForm" (ngSubmit)="onSubmit(userForm.value)">
<table class="table">
<thead>
</thead>
<tbody formArrayName="userGroups">
<ng-container *ngFor="let item of userData; let i = index" formGroupName="{{ i }}">
<tr>
<td colspan="5">
{{item.group}}
</td>
</tr>
<div formArrayName="userDetails">
<tr *ngFor="let info of item.info; let j = index" formGroupName="{{ j }}">
<td>
<input type="checkbox" formControlName="active">
</td>
<td>
{{info.id}}
</td>
<td>
{{info.name}}
</td>
<td>
{{info.number}}
</td>
<td>
<input type="text" formControlName="remarks">
</td>
</tr>
</div>
</ng-container>
</tbody>
</table>
<div>
<button class="btn btn-primary" type="submit">Save</button>
</div>
</form>
</div>

Related

How to make filter on table based on option select

I am working on a project (vue + laravel) where i need to fetch all table structure from db. So my table will be dynamically with all field(table header) and record. Now i need to do a filter(search) for each visible column. I have tryed some way but it does not work for me, I just dont know how to pass only column value for each option select value.
Collapse content is the option who will show on filter click and there should display the option for each column
<table class="table table-hover align-middle mb-0" :class="smallTable.smTable">
<thead class="">
<tr>
<th><input type="checkbox" class="form-check-input" v-model="selectAll" title="Select All"></th>
<th v-for="(header, i) in visibleHeaders" :key="i" scope="col">
{{ header.name }}
</th>
<th v-if="actionHide">ACTION</th>
</tr>
</thead>
<!-- Collapsed content ./ -->
<thead class="collapse" id="collapseFilter">
<tr>
<th></th>
<th v-for="(header, i) in visibleHeaders" :key="i">
<div class="filter-table col-12 d-flex">
<select id="" class="form-select" >
<option v-for="(lead, i) in leads" >{{lead}}</option>
</select>
</div>
</th>
</tr>
</thead>
<!-- ./ Collapse contet -->
<tbody>
<tr v-show="leads.length" v-for="(lead, i) in leads" :key="i">
<td>
<input type="checkbox" class="form-check-input" v-model="selected" :value="lead.id" />
</td>
<td v-for="(field, j) in lead" :key="j">
<span v-if="field == 'new'" class="badge badge-primary">
{{ field }}
</span>
<span v-else-if="field == 'contract'" class="badge badge-success">
{{ field }}
</span>
<span v-else>
{{ field }}
</span>
</td>
<td >
<button #click="editLead(lead.id)" type="button" class="btn btn-sm btn-secondary" data-mdb-toggle="modal" data-mdb-target="#editLeadModal" >
<i class="fa-solid fa-eye"></i>
</button>
</td>
</tr>
<tr v-show="!leads.length">
<td colspan="12" class="text-center">Sorry :( No data found.</td>
</tr>
</table>
data() {
return {
headers: [],
leads: [],
fields: [],
}
}
mounted() {
axios.get('/leads/getfields')
.then(response => {
this.fields = response.data.map(field => {
return {
name: field,
visible: true,
}
});
}
this.getData();
});
}
getData() {
this.headers = this.fields.map(item => {
if (item.visible) {
return item.name;
}
});
axios.post('/leads/getleads?page=' + this.pagination.current_page, {
perPage: this.displayRecord,
fields: this.headers,
})
.then(response => {
this.leads = response.data.data;
this.pagination = response.data.meta
});
},

Array is empty when fetching data in template post axios – vuejs

i try to fetch data from an array returned with vuejs script. i displayed data in script and everything is well. in created function, the value array is empty. but with template. the array is empty and not returned null.
export default {
name: 'attribute-values',
props: ['attributeid'],
data() {
return {
values: [],
value: '',
price: '',
currentId: '',
valueTest:'',
addValue: true,
key: 0
}
},
methods: {
loadValues() {
let attributeId = this.attributeid;
var self = this;
self.valueTest='a test';
axios.post('/admin/attributes/get-values', {
id: attributeId
}).then (function(response){
self.values = response.data;
console.log(self.values);
this.values=self.values;
}).catch(function (error) {
console.log(error);
});
console.log(this.values);
}
},
created :function() {
this.loadValues();
}
}
and this is the template
<tr v-if="values.length==0"><td>No items founds</td></tr>
<tr v-for="valuee in values" :key="valuee.id">
<td style="width: 25%" class="text-center">{{ valuee.id }}</td>
<td style="width: 25%" class="text-center">{{ valuee.value }}</td>
<td style="width: 25%" class="text-center">{{ valuee.price }}</td>
<td style="width: 25%" class="text-center">
<button class="btn btn-sm btn-primary">
<i class="fa fa-edit"></i>
</button>
<button class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
this is the output of the array returned from created function:

vuejs loopin over array of objects as a prop renders incorrectly compared to hardcoding as data

When I try to pass an array of objects to my component as a prop...
[{"prize":"first","abbr":"1st Prize","value":"50"},{"prize":"second","abbr":"2nd Prize","value":"30"},{"prize":"third","abbr":"3rd Prize","value":"20"}]
...I don't get 3 columns that I expected, but I get over 100, and I'm missing my placeholder and data attribute in the text box.
export default {
props: ['settings', 'colour', 'draw_type'],
data: function() {
return {
prizes: {
first: {
name: '',
telephone: ''
},
second: {
name: '',
telephone: ''
},
third: {
name: '',
telephone: ''
},
fourth: {
name: '',
telephone: ''
}
}
}
},
...
...
How can I fix my code so I can pass a given array of objects as a prop and still render it correctly?
When I hardcode settings, the table renders properly:
<template>
<form #submit.prevent="submit" class="mb-5">
<div class="row">
<div class="col-md-3" v-for="(setting, index) in settings">
<p>{{setting.abbr}}</p>
<div class="card">
<div :class="colour">
<div class="box">
{{ setting.prize }}
</div>
</div>
<div class="card-body text-center">
<h5 class="card-title">£{{ setting.value }}</h5>
<div class="form-group">
<input type="text" class="form-control" :placeholder="setting.abbr" maxlength="2" :data-prize="setting.prize" v-model="setting.prize">
</div>
</div>
</div>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Prize</th>
<th>Number</th>
<th>Name</th>
<th>Telephone</th>
</tr>
<tr v-for="(setting, index) in settings">
<td>£{{ setting.value }}</td>
<td>{{ setting.prize }}</td>
<td>{{ prizes[setting.prize].name }}</td>
<td>{{ prizes[setting.prize].telephone }}</td>
</tr>
</table>
<div class="form-group row">
<div class="col-sm-8">
<button type="submit" class="btn btn-primary">
Save Results
</button>
</div>
</div>
</form>
</template>
<script>
export default {
props: ['colour', 'draw_type'],
data: function() {
return {
settings: [
{
"prize":"first",
"abbr":"1st Prize",
"value":"50"
},
{
"prize":"second",
"abbr":"2nd Prize",
"value":"30"
},
{
"prize":"third",
"abbr":"3rd Prize",
"value":"20"
}
],
prizes: {
first: {
name: '',
telephone: ''
},
second: {
name: '',
telephone: ''
},
third: {
name: '',
telephone: ''
},
fourth: {
name: '',
telephone: ''
}
}
}
},
mounted() {
console.log(this.settings);
}
};
</script>
Add key on element v-for maybe is your issue
<tr v-for="(setting, index) in settings" :key="index">
<td>£{{ setting.value }}</td>
<td>{{ setting.prize }}</td>
<td>{{ prizes[setting.prize].name }}</td>
<td>{{ prizes[setting.prize].telephone }}</td>
</tr>
By default, props are string types, so when you pass in an object array, it will be interpreted as a string. The v-for iterates the string as an array of characters, which might explain the 100+ items without the expected properties.
The prop types should be set like this:
export default {
props: {
settings: {
type: Array,
default: () => []
},
colour: String,
draw_type: String
},
}
Vue.component('prizes', {
template: `<table class="table table-striped">
<tr>
<th>Prize</th>
<th>Number</th>
<th>Name</th>
<th>Telephone</th>
</tr>
<tr v-for="(setting, index) in settings">
<td>£{{ setting.value }}</td>
<td>{{ setting.prize }}</td>
<td>{{ prizes[setting.prize].name }}</td>
<td>{{ prizes[setting.prize].telephone }}</td>
</tr>
</table>`,
props: {
settings: {
type: Array,
default: () => []
},
colour: String,
draw_type: String
},
data() {
return {
prizes: {
first: {
name: 'jack',
telephone: '(555) 111-1111',
},
second: {
name: 'jill',
telephone: '(555) 222-2222',
},
third: {
name: 'james',
telephone: '(555) 333-3333',
},
fourth: {
name: 'jane',
telephone: '(555) 444-4444',
}
}
}
}
})
new Vue({
el: '#app',
data: () => ({
settings: [
{
prize: 'first',
abbr: '1st Prize',
value : 50
},
{
prize: 'second',
abbr: '2nd Prize',
value: 30
},
{
prize: 'third',
abbr: '3rd Prize',
value: 20
}
]
}),
})
<script src="https://unpkg.com/vue#2.6.10"></script>
<div id="app">
<prizes :settings="settings"></prizes>
</div>

How to auto-populate form fields using vue and laravel

I'm developing a web application where I want to populate some field if I type computer_number I want to populate staff_old_name field that will select from staffs table.
This is what I've tried:
Template
<div>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Computer Number</th>
<th>Old Name</th>
<th>New Name</th>
<th>Remarks</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(staff, index) in staffs">
<td>
<span v-if="staff.editmode"><input class="form-control" v-model="staff.computer_number"/></span>
<span v-else>{{staff.computer_number}}</span>
</td>
<td>
<span v-if="staff.editmode"><input class="form-control" v-model="staff.old_name"/></span>
<span v-else>{{staff.old_name}}</span>
</td>
<td>
<span v-if="staff.editmode"><input class="form-control" v-model="staff.new_name"/></span>
<span v-else>{{staff.new_name}}</span>
</td>
<td>
<span v-if="staff.editmode"><input class="form-control" v-model="staff.remarks"/></span>
<span v-else>{{staff.remarks}}</span>
</td>
<td>
<span v-if="!staff.editmode"><button class="btn btn-info" type="button" #click="edit(staff)">Edit</button></span>
<span v-else><button class="btn btn-success" type="button" #click="save(staff)">Save</button></span>
<span><button type="button" class="btn btn-danger" #click="remove(index)"><i class="fa fa-trash"></i></button></span>
</td>
</tr>
</tbody>
</table>
<div class="box-footer">
<button class="btn btn-info" type="button" #click="cloneLast">Add Row</button>
</div>
</div>
Script
export default {
data() {
return {
staffs: [],
data_results: []
}
},
computed:{
autoComplete(){
this.data_results = [];
if(this.computer_number.length > 2){
axios.get('/api/staffs/autocomplete',{params: {computer_number: this.computer_number}}).then(response => {
console.log(response);
this.data_results = response.data;
});
}
}
},
methods: {
edit :function(obj){
this.$set(obj, 'editmode', true);
},
save : function(obj){
this.$set(obj, 'editmode', false);
},
remove: function(obj){
this.staffs.splice(obj,1);
},
cloneLast:function(obj){
//var lastObj = this.staffs[this.staffs.length-1];
//lastObj = JSON.parse(JSON.stringify(lastObj));
obj.editmode = true;
this.staffs.push(obj);
},
},
created() {
axios.get('/staff-names')
.then(response => this.staffs = response.data);
},
}
when I type the computer number I want the staff_old_name field populated base on staff name which is stored in staffs table.

How to get value from Dynamic Dropdown and submit it in database in VueJs and Laravel

I am creating a form in which you add cities on the basis of countries you get from dynamic drop-down. I am able to get the category_id (foreign key) in Vuejs but I don't how to pass it to the laravel back-end. I want to create a subcategory object with name and category_id. category_id is foreign key. I am unable to get category_id and it is passing NULL to db.
**Category = Country**
**Subcategory = City**
<template id="add-post">
<div>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>City Name</th>
<th>Country id</th>
</tr>
</thead>
<tbody>
<tr v-for="(subcategory, index) in citylist" :key="subcategory.id">
<td>{{ index + 1 }}</td>
<td>{{ subcategory.name }}</td>
<td>{{ subcategory.category_id }}</td>
</tr>
</tbody>
</table>
<h3>Add City</h3>
<form v-on:submit.prevent = "createPost">
<select v-model="countryid" name="country" class="form-control" #change="myFunction()">
<option countryid disabled>Select Country</option>
<option v-for="category in categories" v-bind:value='category.id' v-text="category.name">{{category.name}}</option>
</select>
<div class="form-group">
<label for="add-name">Name</label>
<input v-model="subcategory.name" class="form-control" required />
</div>
<button type="submit" class="btn btn-xs btn-primary">Add City</button>
<!-- <button v-on:click="setPostId()">click</button> -->
<router-link class="btn btn-xs btn-warning" v-bind:to="'/'">Cancel</router-link>
</form>
</div>
</template>
<script>
export default {
data: function () {
return {
categories: [],
category: '',
countryid:'',
subcategories: [],
subcategory: {name: '', category_id: ''}
};
},
mounted(){
this.loadCategories()
},
created: function() {
let uri = 'http://localhost:8000/cities/';
Axios.get(uri).then((response) => {
this.subcategories = response.data;
});
},
computed: {
citylist: function(){
if(this.subcategories.length) {
return this.subcategories;
}
}
},
methods: {
myFunction: function() {
console.log(this.countryid)
},
loadCategories(){
axios.get('/api/categories')
.then(response => this.categories = response.data)
.catch(error => console.log(error))
},
createPost: function() {
let uri = 'http://localhost:8000/addsubcategory/';
Axios.post(uri, this.subcategory).then((response) => {
this.$router.push({name: 'City'})
})
}
}
}
</script>
you can make drop down
<select v-model="fruit" id="deptList">
<option v-for="company_master in company_masters.data" v-bind:value="company_master.CompanyId"> {{company_master.CompanyName}}
</option>
</select>
and call the data with axios like
axios.get("api/company_master").then(response => (this.company_masters = response.data));
also define
company_masters:[],
fruit:'',

Resources