FormArray does not print on my Reactive Form - arrays

I'm starting at Angular and I'm having a really hard time setting a FormArray inside my form. In summary what I did was:
Create 2 forms: the main (valForm) and another one created dynamically (holidaysForm).
Copy the values of the second form to an Array.
Load the values of the Array into a FormArray.
Follow the codes:
.ts
let group = {}
this.holidaysForm = new FormGroup(group);
this.valForm = fb.group({
dentistId: [null, Validators.required],
initialHour: [null, Validators.required],
endHour: [null, Validators.required],
numberOfHolidays: [null],
appointmentsInterval: [null, Validators.required],
year: [null, Validators.required],
workDays: this.buildDays(),
holidays: this.buildHolidays()
});
buildDays() {
const values = this.workDays.map(v => new FormControl(false));
return this.fb.array(values);
}
buildHolidays() {
if (typeof this.valForm !== 'undefined') {
let teste = Object.assign({}, this.holidaysForm.value);
this.holidays = new Array();
Object.values(teste).forEach((v) => {
this.holidays.push(v);
})
console.log(this.holidays);
const values = this.holidays.map((v)=> new FormControl(v));
return this.fb.array(values);
}
}
dynamicForm(val) {
if (val > 365) {
val = 365;
this.valForm.patchValue({
numberOfHolidays: 365
})
}
const val_plus_one = parseInt(val) + 1
let i: number = val_plus_one;
if (i < this.oldNumber) {
do {
this.holidaysForm.get('holiday' + i.toString()).setValue(null);
this.holidaysForm.removeControl('holiday' + i.toString());
i++
} while (i <= this.oldNumber)
}
const numbers = Array(val).fill(val, 0, 365).map((_, idx) => idx + 1)
numbers.forEach((num) => {
const fc = new FormControl('', FormValidations.Calendar(this.valForm.get('year').value));
this.holidaysForm.addControl('holiday' + num.toString(), fc)
})
this.numberOfHolidays = numbers;
this.oldNumber = val;
}
.html
<div class="col-md-4 mda-form-group">
<input class="mda-form-control" type="number" min="0" max="365"
formControlName="numberOfHolidays"
(change)="dynamicForm(valForm.get('numberOfHolidays').value)" />
<label>Total de Feriados</label>
</div>
<div [formGroup]="holidaysForm">
<div class="mda-form-group" *ngFor="let num of numberOfHolidays">
<input class="mda-form-control" type="date" formControlName="holiday{{num}}" />
<label>Feriado {{num}}</label>
<app-error-msg [control]="holidaysForm.get('holiday'+num)" label="Feriado">
</app-error-msg>
</div>
</div>
In theory the code works well and without errors, the problem is that the result is always this:
Main Form
Valores:
{
"dentistId": null,
"initialHour": null,
"endHour": null,
"numberOfHolidays": 3,
"appointmentsInterval": null,
"year": null,
"workDays": [
false,
false,
false,
false,
false,
false,
false
],
"holidays": null
}
Holiday Form
{
"holiday1": "2020-01-01",
"holiday2": "2020-03-01",
"holiday3": "2020-02-01"
}
Does anyone have any idea what I might be doing wrong? Thankful,

The problem seems to be you are using holidaysForm before you ever put values in it. So at the point where you are creating the object to loop through to create your array, that object will have no properties.
buildHolidays() {
if (typeof this.valForm !== 'undefined') {
let teste = Object.assign({}, this.holidaysForm.value); // this line
this.holidays = new Array();
Object.values(teste).forEach((v) => {
this.holidays.push(v);
})
console.log(this.holidays);
const values = this.holidays.map((v)=> new FormControl(v));
return this.fb.array(values);
}
}
I would figure out how to get values to use in this method to make it all work. And remember, you can always add a formArray after the creation of your original formgroup via group.addControl('controlName', myFormArray);
Best of luck, and as always
Happy Coding

Related

Avoid pushing duplicate objects to an angular array

I have multiple checkboxes in my angular application. When user checked and unchecked checkboxes I want to pass those true/false values into an array. It's happening from below code.
But my problem is as you can see the below console.log, it has duplicate checkbox values(index 0 and 3 have same thing) and push it to the array.
I want to know how to check duplicate objects and avoid pushing object to the array.
.ts file
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
const layer = {
isChecked: isChecked,
id: id,
}
this.layers.push(layer);
console.log(this.layers);
}
.html file
<input id="population" (change)="layerChange($event)" type="checkbox">
<input id="gender" (change)="layerChange($event)" type="checkbox">
<input id="householdIncome" (change)="layerChange($event)" type="checkbox">
console.log(this.layers)
**0: {isChecked: true, id: 'population'}**
1: {isChecked: true, id: 'age'}
2: {isChecked: false, id: 'population'}
**3: {isChecked: true, id: 'population'}**
You can check if an entry exists first using either :
var id = e.target.attributes.id.nodeValue;
var attr = this.layer.find(x => x.id === id);
if(!attr)
this.layers.push(layer);
or
this.layer.filter(x => x.id === id)
Edit
in your scenario is better to construct the array only one time in page load.
ngOnInit(): void {
this.layer = [{id: 'age', isChecked: false}, {id:'population',isChecked: false}]
}
and then alter the check state when user check/uncheck :-
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
this.layer.find(x => x.id === id).isChecked = isChecked;
console.log(this.layers);
}
You can create a string array which contains only ids and you can insert or remove elements from the array as per the selection
layerChange(e:any) {
const id = e.target.attributes.id.nodeValue;
const index = this.layers.findIndex(el => el === id)
if(index === -1) {
this.layers.push(id);
} else {
this.layers.splice(index, 1)
}
console.log(this.layers);
}
my GOD!
Kalana (and others) we need re-thinking the problem using "variables". Yes, Angular philosophy is binding variables. Variables in .ts makes the values are showed in the .html
So, some simple like declare an array of object in the .ts
layers:any[]=[{isChecked: true, id: 'population'},
{isChecked: true, id: 'age'},
{isChecked: false, id: 'population'}
]
Allow us write in .html
<ng-container *ngFor="let layer in layers">
<input type="checkbox" [(ngModel)]="layer.isChecked"
(change)="onChange()">
</ng-container>
In .ts we has a function:
onChange(){
console.log(this.layers)
}
Finally , I was able to find a solution. Thank you.
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
const index = this.layers.findIndex(el => el.id === id);
const layer = {
isChecked: isChecked,
id: id,
}
if(index > -1){
this.layers[index].isChecked = isChecked;
}else{
this.layers.push(layer);
}
console.log(this.layers);
}

How do I validate a checkout form in React?

I am trying to implement a checkout form in React. The form has 4 fields in all: Name, CC Number, CC expiration and CVV. I am using a library that validates each field on unfocus. The validation is triggered by the validationCallback method which takes 3 arguments: field, status, and message. I'd like to key off of the status for each input and only allow submit once each status === true. Here is my code.
constructor(props) {
super(props);
this.state = {
nameOnCard: '',
errorMessage: '',
showLoaderForPayment: '',
collectJs: null,
token: null,
isPaymentRequestCalled: false,
showErrorModal: false,
paymentErrorText: '',
disabled: true,
};
}
I have a disabled property in my state which I'm initially setting to true.
validationCallback: (field, status, message) => {
if (status) {
this.setState({ errorMessage: '' });
} else {
let fieldName = '';
switch (field) {
case 'ccnumber':
fieldName = 'Credit Card';
break;
case 'ccexp':
fieldName = 'Expire Date';
break;
case 'cvv':
fieldName = 'Security Code';
break;
default:
fieldName = 'A';
}
if (message === 'Field is empty') {
this.setState({ errorMessage: `${fieldName} ${message}` });
} else {
this.setState({ errorMessage: `${message}` });
}
}
},
In the above method, I'd like to set disabled to false if each of the field's status===true... Below is the button which I'm setting to be the value of this.state.disabled.
<button
className="continueBtn disabled"
disabled={this.state.disabled}
onClick={this.handleCardSubmit}
>
<span className="fa fa-lock" />
Pay $
{selectedPayment.amount}
</button>
I hope this is enough of the code to help with the issue. I can provide more of the file if need be.
From what i understand, you want to set the button to NOT DISABLED if all the fields are filled properly, i.e. all status are true.
What you can do is maintain a boolean array for each field and update the status in that array, i.e. initialize an array of length = no. of fields (in your case 3) and set all values as false. False depicts that the field hasn't been validated.
this.state = {
statusArray = [false, false, false] // For as many fields
}
Then in validationCallback, set the index as true or false for that field i.e. if the 2nd field status is returned true by your validation library, set statusArray as [false, true, false].
The form will only be validated if all 3 of the values become true. So you can iterate over the array and check if array has all 3 values as true. or you can use the logical AND operator which returns true only if all values are true(the approach which i use below).
For the button,
<button disabled={this.checkDisable()}>
checkDisable = () => {
let temp = this.state.statusArray;
let answer = true;
for(int i=0;i<temp.length;i++)
answer = answer && temp[i];
return answer; // Only returns true if all 3 values are true
}
I hope you get it now.
You need to check 2 things, has the form been touched and are there any errors. I don't know what library you are using but most likely it has a property touched in it, if not add an onFocus to each input field and a touched property in your state. You don't really need a disabled property in your state since its a computed value. Just check on every render if the form has been touched and if there are any errors.
state = {
...,
touched: false,
...
}
handleFocus = () => this.setState({touched: true})
render(){
const disabled = !!(this.state.touched && this.state.errorCode)
return(
...
<input onFocus={this.handleFocus} ... />
...
<button disabled={disabled}
)
}
EDIT:
state = {
...
validInputs: []
}
validationCallback: (field, status, message) => {
if (status) {
this.setState((state) => ({ errorMessage: '', validInputs: [... new Set([...state.validInputs, field])] }));
} else {
...
render(){
const disabled = this.state.length < inputs.length // the number of the input fields
return(
...
<button disabled={disabled} >
...
)

Datatable-angular export data buttons only creating column headers not data

Export buttons are only exporting column headers not the data.
I am using datatables in angular5, with serverside processing.
Previously i was using clint-side processing with all data and it was working fine, later i moved for server-side processing with below code.
this.table = $('#my-data-table').DataTable({
serverSide: true,
filter: false,
sort: true,
orderCellsTop: true,
columnDefs: [{
targets: [-1, 0, 1, 2, 4, 10, 12],
orderable: false,
}],
ajax: (dataTablesParameters: any, callback) => {
this.draw += 1;
let info = $('#early-detection-data-table').DataTable().page.info();
if (info.length > 10) {
if (info.page > 0) {
this.offset = ((info.length) * (info.page));
} else {
this.offset = (((info.page + 1) * 10) - 10);
}
} else {
this.offset = (((info.page + 1) * 10) - 10);
}
this.countNumber = (this.offset + 1);
let limit = info.length;
this.patientService.getRecentTransmission(limit.toString(), this.offset.toString(), this.searchCriterian).subscribe(
(data: any) => {
this.earlyDetections = data;
let total: number;
$('.showbox').css('display', 'none');
//this.setVisiblility();
if (data[0] && data[0].recordsTotal > 0) {
total = data[0].recordsTotal;
} else {
total = 0;
}
callback({
recordsTotal: total,
recordsFiltered: total,
data: [],//JSON.stringify(data),
});
console.log(data)
if (data && data.length != 0) {
$('td.dataTables_empty').hide();
} else {
$('td.dataTables_empty').show();
}
});
}, loadingIndicator: true,
dom: 'lBfrtip',
// "order": [[ 7, "desc" ]],
buttons: {
buttons: [
{ extend: 'print', className: 'btn btn-primary btn-round' },
{ extend: 'excel', className: 'btn btn-primary btn-round' },
{ extend: 'pdf', className: 'btn btn-primary btn-round' }
]
},
});
Please help me and let me know if anything else required from my side.
Last week I had the same issue.
I spent almost 2 days to solve it, the solution that I come with was:
Go to the export buttons library and find where the data was built before exporting to file.
You will find there:
var = data;
data = {
body: [] //actual data of the table
footer: [] //footer of the table if exists
header: [] //header of the table
}
In my case here is the data object:
body: []
footer: null
header: (4) ["#", "Serial Number", "Filter Type", ""]
__proto__: Object
So as you can see the body is empty and that is why you get only headers are shown.
What I did is building the body from the response of the server call and store it on the local storage(so I can access it from everywhere).
Here is the code sample:
const TableData = [...resp.controllers];
this.utilService.getDataArrayForExport(TableData, 'Controllers');
getDataArrayForExport(TableData, TableType) {
const DataArray = [];
let formatedObject = {};
TableData.forEach(function (object, i) {
switch (TableType) {
case "Clients":
formatedObject = {
'email': object.email,
'name': object.name,
'jobDescription': object.jobDescription,
'company': object.company,
'countries': object.countries
};
break;
case "Controllers":
formatedObject = {
'controllerSN': object.controllerSN,
'filterType': object.filterType
};
break;
case "Flushes":
const pipeFlush = new DatePipe('en-US');
const timeFlush = pipeFlush.transform(object.time, 'short');
formatedObject = {
'controllerSN': object.controllerId,
'description': object.description,
'time': timeFlush,
'dpBefore': object.dpBefore,
'dpAfter': object.dpAfter
};
break;
case "Alerts":
const pipe = new DatePipe('en-US');
const time = pipe.transform(object.time, 'short');
formatedObject = {
'controllerSN': object.controllerId,
'description': object.description,
'time': time,
};
break;
case "Admins":
formatedObject = {
'email': object.email,
'jobDescription': object.jobDescription,
'company': object.company,
'countries': object.countries
};
break;
}
const array = [];
array.push((i + 1).toString());
$.each(formatedObject, function (key, value) {
array.push(value);
});
DataArray.push(array);
});
localStorage.setItem('export', JSON.stringify(DataArray));
console.dir(DataArray);
}
Now add a small logic on export if data.body is an empty array:
var data = dt.buttons.exportData( config.exportOptions );
var ddd = JSON.parse(localStorage.getItem("export"));
if(data.body.length === 0){
data.body = ddd;
}
Now the data object is populated with the data:
body: (10) [Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3), Array(3)]
footer: null
header: (4) ["#", "Serial Number", "Filter Type", ""]
__proto__: Object
So now all set, and you will get all the data export correctly.
I hope my post will help you.
If you have some questions you are welcome to ask :)

loop through objects inside an array in a regex

EDITED
I got an array of objects:
values: [
{ key: "CollinHenderson" },
{ key: "SarahDrasner" },
{ key: "EvanYou" },
{ key: "AdamWathan" }
]
I want to highlight the text if one of them is mentioned ("#..."):
.innerHTML
.replace(
/(?:|^)#[A-Za-z0-9\\-\\.\\__äÄöÖüÜß]+(?:|$)/g,
"<span contenteditable='false' class='markAt'>$&</span>"
);
with this regex (above) everything gets highlighted which starts with a '#'. How could I loop trough my array of objects inside of my regex, so that it only turns out true if one of my users get mentioned. For Example:
"#hello" should be false
"#CollinHenderson" should be true and get therefore highlighted
/(?:|^)#[ //loop through array - if matches a value -> true // ]+(?:|$)/g,
You can pass a function to replace and check in there
const values = [{
key: "CollinHenderson"
},
{
key: "SarahDrasner"
},
{
key: "EvanYou"
},
{
key: "AdamWathan"
}
]
const el = document.getElementById('foo');
el.addEventListener('keyup', e => {
document.getElementById('bar').innerHTML = e.target.value
.replace(
/(?:|^)#[A-Za-z0-9\\-\\.\\__äÄöÖüÜß]+(?:|$)/g,
m => {
if (values.find(v => m.substring(1) === v.key)) return "<span contenteditable='false' class='markAt'>"+ m + "</span>";
return m;
}
);
});
.markAt {
font-weight: 700;
}
<textarea id="foo"></textarea>
<div id="bar">
</div>

Vue.js: Manipulate Array and post form with new data

In my Vue.js application I want to post form data to my Node.js/MongoDB Backend.
This is my source code: https://github.com/markusdanek/t2w-vue/blob/master/src/components/backend/JobEdit.vue
JSON for my job entry: http://t2w-api.herokuapp.com/jobs/591c09a55ba85d0400e5eb61
Relevant code for my question:
HTML:
<div class="row">
<input type='text'
:name="'qual'+index"
v-model="qualifications[index]">
<button #click.prevent="removeQualifiaction(index)">X</button>
</div>
Methods:
onChange(value, $event){
if (!this.job.xmlOnline)
this.job.xmlOnline = []
const index = this.job.xmlOnline.findIndex(v => v == value)
const checked = $event.target.checked
if (checked && index < 0)
this.job.xmlOnline.push(value)
if (!checked && index >= 0)
this.job.xmlOnline.splice(index, 1)
}
removeQualifiaction() {
this.qualifications.splice(this.qualifications.index, 1);
}
Sending the form data with submit button on form end:
editJob() {
let job = Object.assign({}, this.job);
job.qualifications = this.qualifications;
job.responsibility = this.responsibility;
this.$http.post('https://t2w-api.herokuapp.com/jobs/' + this.$route.params.id, job).then(response => {
console.log(response);
}, response => {
console.log(response);
});
}
My problems now:
When I edit a "Job", I have a list of "qualification items", that are input fields in my form.
Clicking the "delete" button results that the first input gets deleted, not the one I am clicking. Done with #thanksd answer.
How do I add a button and method to add a new input field and to append it to my job.qualifications?
In my JobAdd.vue implemented, to add a new entry to job.qualifications, like this:
<a #click.prevent="addQualification">+</a>
addQualification() {
this.qualification.push({ text: '' });
}
addJob() {
let job = Object.assign({}, this.job);
job.qualifications = this.qualification.map(q => q.text);
this.$http.post('https://t2w-api.herokuapp.com/jobs/', job).then(response => {....
Full source for my JobAdd.vue: https://github.com/markusdanek/t2w-vue/blob/master/src/components/backend/JobAdd.vue
this.qualification.push({ text: '' }); doesnt work obviously not in my JobEdit.vue when there are already strings in my job.qualifications.
Change your removeQualifiaction method to use the index being passed in:
removeQualifiaction(index) {
this.qualifications.splice(index, 1);
}

Resources