I was working on a project of multiple checkbox. There, I want the checkboxes to be checked from the start and the value to be in the form(I am using reactive form). The user can unselect the boxes according to their wish and the data will be stored accordingly. This is the stackblitz of the project. There I was able to make the checkbox checked from the beginning, but when I hit the submit button there is no data when I console-logged. I think this is some binding issue,but I couldn't figure out what is exactly the problem.
Can someone help?
Thanks in advance.
This is code:
<form [formGroup]="form" (ngSubmit)="submit()">
<div class="form-group">
<label for="website">Website:</label>
<div *ngFor="let web of websiteList">
<label>
<input
type="checkbox"
[value]="web.id"
(change)="onCheckboxChange($event)"
[checked]="web.isSelected"
/>
{{ web.name }}
</label>
</div>
</div>
<button class="btn btn-primary" type="submit">Submit</button>
</form>
form: FormGroup;
websiteList: any = [
{ id: 1, name: 'HDTuto.com', isSelected: true },
{ id: 2, name: 'HDTuto.com', isSelected: true },
{ id: 3, name: 'NiceSnippets.com', isSelected: true },
];
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
website: this.fb.array([], [Validators.required]),
});
}
ngOnInit() {}
onCheckboxChange(e: any) {
const website: FormArray = this.form.get('website') as FormArray;
console.log('checking ->', e);
if (e.target.checked) {
website.push(new FormControl(e.target.value));
console.log('website ->', website);
} else {
//console.log(e);
const index = website.controls.findIndex((x) => {
console.log('x.value ->', x.value);
console.log('target.value ->', e.target.value);
x.value === e.target.value;
});
website.removeAt(index);
}
}
submit() {
console.log(this.form.value);
}
https://stackblitz.com/edit/angular-ivy-qar4ph?file=src/app/app.component.ts
Pay attention to changes in template:
Added formArrayName attribute to checkboxes wrapper and formControlName attribute to input element.
Removed change and checked attributes
In the component ts file:
Added initial form array values
Added mapping to submit method
Removed onCheckboxChange method
Related
I have a vue-cli project, that has a component named 'AutoCompleteList.vue' that manually handled for searching experience and this component has some buttons that will be fill out the input.
It listens an array as its item list. so when this array has some items, it will be automatically shown; and when I empty this array, it will be automatically hidden.
I defined an oninput event method for my input, that fetches data from server, and fill the array. so the autocomplete list, will not be shown while the user doesn't try to enter something into the input.
I also like to hide the autocomplete list when the user blurs the input (onblur).
but there is a really big problem! when the user chooses one of items (buttons) on the autocomplete list, JS-engine first blurs the input (onblur runs) and then, tries to run onclick method in autocomplete list. but its too late, because the autocomplete list has hidden and there is nothing to do. so the input will not fill out...
here is my code:
src/views/LoginView.vue:
<template>
<InputGroup
label="Your School Name"
inputId="schoolName"
:onInput="schoolNameOnInput"
autoComplete="off"
:onFocus="onFocus"
:onBlur="onBlur"
:vModel="schoolName"
#update:vModel="newValue => schoolName = newValue"
/>
<AutoCompleteList
:items="autoCompleteItems"
:choose="autoCompleteOnChoose"
v-show="autoCompleteItems.length > 0"
:positionY="autoCompletePositionY"
:positionX="autoCompletePositionX"
/>
</template>
<script>
import InputGroup from '../components/InputGroup'
import AutoCompleteList from '../components/AutoCompleteList'
export default {
name: 'LoginView',
components: {
InputGroup,
AutoCompleteList
},
props: [],
data: () => ({
autoCompleteItems: [],
autoCompletePositionY: 0,
autoCompletePositionX: 0,
schoolName: ""
}),
methods: {
async schoolNameOnInput(e) {
const data = await (await fetch(`http://[::1]:8888/schools/${e.target.value}`)).json();
this.autoCompleteItems = data;
},
autoCompleteOnChoose(value, name) {
OO("#schoolName").val(name);
this.schoolName = name;
},
onFocus(e) {
const position = e.target.getBoundingClientRect();
this.autoCompletePositionX = innerWidth - position.right;
this.autoCompletePositionY = position.top + e.target.offsetHeight + 20;
},
onBlur(e) {
// this.autoCompleteItems = [];
// PROBLEM! =================================================================
}
}
}
</script>
src/components/AutoCompleteList.vue:
<template>
<div class="autocomplete-list" :style="'top: ' + this.positionY + 'px; right: ' + this.positionX + 'px;'">
<ul>
<li v-for="(item, index) in items" :key="index">
<button #click="choose(item.value, item.name)" type="button">{{ item.name }}</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'AutoCompleteList',
props: {
items: Array,
positionX: Number,
positionY: Number,
choose: Function
},
data: () => ({
})
}
</script>
src/components/InputGroup.vue:
<template>
<div class="input-group mb-3">
<label class="input-group-text" :for="inputId ?? ''">{{ label }}</label>
<input
:type="type ?? 'text'"
:class="['form-control', ltr && 'ltr']"
:id="inputId ?? ''"
#input="$event => { $emit('update:vModel', $event.target.value); onInput($event); }"
:autocomplete="autoComplete ?? 'off'"
#focus="onFocus"
#blur="onBlur"
:value="vModel"
/>
</div>
</template>
<script>
export default {
name: 'input-group',
props: {
label: String,
ltr: Boolean,
type: String,
inputId: String,
groupingId: String,
onInput: Function,
autoComplete: String,
onFocus: Function,
onBlur: Function,
vModel: String
},
emits: [
'update:vModel'
],
data: () => ({
}),
methods: {
}
}
</script>
Notes on LoginView.vue:
autoCompletePositionX and autoCompletePositionY are used to find the best position to show the autocomplete list; will be changed in onFocus method of the input (inputGroup)
OO("#schoolName").val(name) is used to change the value of the input, works like jQuery (but not exactly)
the [::1]:8888 is my server that used to fetch the search results
If there was any unclear code, ask me in the comment
I need to fix this. any idea?
Thank you, #yoduh
I got the answer.
I knew there should be some differences between when the user focus out the input normally, and when he tries to click on buttons.
the key, was the FocusEvent.relatedTarget property. It should be defined in onblur method. here is its full tutorial.
I defined a property named isFocus and I change it in onBlur method, only when I sure that the focus is not on the dropdown menu, by checking the relatedTarget
I would like to know how to set checked to radio button using litelement.
I have a object and for each object options, radio button is created.
For example, for id=SG two radio buttons are created,
if no checked, set bank as default checked
else set corresponding selected radio value as checked.
I got stuck in litelement.
const obj= [{
id: "SG",
options: ["bank", "credit"]
},
{
id: "TH",
options: ["bank"]
}
];
render(){
${obj.map((e)=>{
return html`
<form>
${obj.options.map((option_value)=>{
return html`
<input class="form-check-input" name="sending-${option_value}" type="radio" id="provider-send-${option_value}" value=${option_value} ?checked=${option_value=="bank"} > // not working
<label class="form-check-label">
${option_value}
</label><br>
`})}
</form>
})`;
}
Expected Output:
Set checked to corresponding radio selected
If no checked, set bank as default checked
This sets the checked attribute to true if the option is bank:
import { LitElement, html } from 'lit-element';
class TestElement extends LitElement {
static get properties() {
return {
countries: {
type: Array,
},
};
}
constructor() {
super();
this.countries = [
{
id: 'SG',
options: ['bank', 'credit'],
},
{
id: 'TH',
options: ['bank'],
},
{
id: 'MY',
options: ['credit'],
}
];
}
render() {
return html`
${this.countries.map(country => html`
<fieldset>
<legend>${country.id}</legend>
<form>
${country.options.map(option => html`
<input
id="provider-send-${option}"
name="sending-${country.id}"
type="radio"
class="form-check-input"
value="${option}"
?checked=${option === 'bank'}
>
<label class="form-check-label">${option}</label>
<br>
`)}
</form>
</fieldset>
`)}
`;
}
}
customElements.define('test-element', TestElement);
Looks like you just missed mapping the actual obj (country in my snippet).
Also, in order to change the selected radio, the name should be the same for all radios in a group. Your code is setting a different name to each radio.
Inputs I'm creating are unDisabling all at once as the state changes.
How can I enable/disable direct input? Mb something wrong with refs... I dunno
class TaskRow extends Component {
constructor(props) {
super(props);
this.myRef = createRef();
this.state = {
edit: true,
};
}
editTask = () => {
this.setState({ edit: false })
};
render() {
const { edit } = this.state;
return (
<div>
<ul className="taskList">
<div className="tasksContainer">
{tasks.map(task => (
<div className="taskDiv" key={task.id}>
<input type="checkbox" checked={task.completed}/>
<input
ref={this.myRef}
type="text"
content={task.text}
disabled={edit}
/>
<button onClick={this.editTask}>Редактировать</button>
</div>
))}
</div>
</ul>
</div>
)
}
}
What you can do is in constructor,
const inputState = {};
tasks.forEach((task) => inputState[`input_${task.id}] = true)
this.state = {
...inputStates
}
Then in JSX
disabled={this.state[`input_${task.id}`]}
<button onClick={() => this.editTask(`input_${task.id})}>Редактировать</button>
Then in Function
editTask = (inputState) => {
this.setState({ [inputState]: false })
};
I'm just giving you idea, Maintain separate states for each, set them apart. Look for spelling mistakes if any
This is because you are using single state i.e. edit for all the input fields. So, when the state is updated, its updated for all the fields. You have to use different states for different input. One of the suggestion might be that, You can use array of boolean in the edit state update it according to the input fields actions
Since you are binding all the inputs to the single this.state.editTask property, whenever you'll modify it, all inputs will change because all the inputs have their disabled attributes bound to {this.editTask}.
What you can do is that you can define an edit property in each task same as you're tackling the complete property for checkboxes.
Please see the StackBlitz example I've created to see it working.
I.e. tasks would be:
[{
id: 1,
completed: true,
edit: true
}, {
id: 2,
completed: false,
edit: false
}, {
id: 3,
completed: true,
edit: true
}, {
id: 4,
completed: false,
edit: true
}, {
id: 5,
completed: true,
edit: false
}
]
And then you can bind the html in the following way:
<ul className="taskList">
<div className="tasksContainer">
{this.state.tasks.map(task => (
<div className="taskDiv" key={task.id}>
<input type="checkbox" checked={task.completed}/>
<input className='taskInput'
type="text"
content={task.text}
disabled={task.edit !== true}
/>
<button onClick={() => { this.editTask(task) }}>Редактировать</button>
</div>
))}
</div>
</ul>
I am making angular 6 application where i am using angular reactive form.
Html:
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" style="display: flex">
<select (ngModelChange)="changeAction($event)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
Ts:
form: FormGroup;
showLabel: boolean = false;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
}
addCreds() {
const creds = this.form.controls.credentials as FormArray;
creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
}
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
Working stackblitz: https://stackblitz.com/edit/angular-form-array-example-yksojj
In this given example there will be an add button, upon clicking that button you will get a select-box with values as set,wait,go and an input called name.. Upon click over add button the same row will be added and forms each object inside array.
Also you can see an if condition inside html for label,
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
The thing i am in the need was in the select box if i choose set or go then the label needs to displayed otherwise it should not be displayed for which i have written,
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
To be clear enough If the user clicks three times add button then the dropdown and name field alone needs to be displayed for three times whereas if the user selects the value from dropdown as set or go then the label input needs to be displayed to that particular row alone where the dropdown has the value set and go.. If the selection was wait then there should not be label box for the row which has dropdown value as wait.
Kindly help me to achieve the expected result..
If you add disabled:true property to formControl it will exclude fromControl from formGroup then you can enable formControl manually
creds.push(this.fb.group({
action: '',
name: '',
label: {disabled:true, value: ""}
}));
Then enable using enable method
changeAction(e,index) {
if(e === "set" || e === "go") {
this.showLabel = true;
this.form.get('credentials').at(index).get('label').enable();
} else {
this.showLabel = false;
this.form.get('credentials').at(index).get('label').disable();
}
}
Example:https://stackblitz.com/edit/angular-form-array-example-5buwyr
Please, NOT use (ngModelChange) or changeAction($event) to get the value of a control in an array -well, ngModelChange is for Template driven form, not for Reactive Form.
First change your form, create the form using a div with formArrayName="credentials", and a inner div *ngFor="let creds of form.get('credentials').controls
<!--a div with formArrayName--->
<div formArrayName="credentials" >
<!--a div *ngFor over "controls", in this div don't forget formGroupName-->
<div *ngFor="let creds of form.get('credentials').controls; let i = index"
[formGroupName]="i" style="display: flex">
<select formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}">
{{option.value}}
</option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="??????????????">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
Well, now the condition. To get the value of "action" you can use
form.get('credentials').at(i).value.action
//or, better
creds.value.action
So, your div becomes like
<div *ngIf="creds.value.action=='set' ||
creds.value.action=='go'">
<input placeholder="Label" formControlName="label">
</div>
This aproach avoid unnecesary code in your .ts.
this.showLabel
The scope of this variable is your whole component. Therefore, turning it on or off will show and hide all inputs.
You need a per-row value (creds.showLabel in your interface), or use this in your template :
*ngIf="['set','go'].includes(creds.action)"
Updated Stackblitz
By the way, this :
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
is more elegant written this way :
changeAction(e) {
this.showLabel = ['set','go'].includes(e)
}
or this way :
changeAction(e) {
this.showLabel = e in ['set','go']
}
Well, Jeremy's answer is pretty nice and enforces to use most of the native apis given by platform/language, however, here is a traditional approach to understand what is the actual flow and scope of objects, etc,etc...
Root Cause: The show hide field is made global to the scope of component, not at per formgroup level. So changing a single value, and using for all, will affect to all.
Solution:
Use Jeremy's answer for clean coding and less error prone code.
Manage an array of variable that will take care of sho/hide detail for each form group.
In the below answer, added some comments for easy understanding, and added console.log to see what exactly happening. Also, played with index i created in *ngFor and showed how you can make use of these things in future.
import { Component } from '#angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
})
export class AppComponent {
form: FormGroup ;
showLabel = [];
creds : FormArray;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
this.creds = this.form.controls.credentials as FormArray;
}
addCreds() {
this.creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
// every time adding new foem grp, adding lable show/hide data also.
this.showLabel.push(false);
}
changeAction(e, i) {
//every time input clikced, managing show/hide for that particular data also
console.log("iii", i, e.target.value);
if( e.target.value == "set" || e.target.value == "go") {
this.showLabel[i] = true;
} else {
this.showLabel[i] = false;
}
}
}
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" >
<select (change)="changeAction($event, i)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel[i]">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
See live stackblitz working code
Note: Traditional is meant here.. as we every time do i.e. handle our problem on our own and create new problem on us to solve. It's not a tradition. :P
I wrote the below code snippet, which I considered as it will disable the FormControl in a FormArray.
some.component.html
<form [formGroup]="testForm">
<div *ngFor="let num of countArr">
<input type="text" formNameArray="arrs">
</div>
</form>
some.component.ts
countArr = [1, 2, 3, 4, 5];
count = 5;
arrs;
testForm: FormGroup;
this.testForm = this.formBuilder.group(
arrs: this.formBuilder.array([])
);
this.arrs = this.testForm.get('arrs');
for (let i = 0; i < this.count; i++) {
this.arrs.insert(i, new FormControl({value: '', disabled: true}));
}
But after for execution completed, I checked the form and found nothing has been disabled. Can you please tell me where I am doing wrong??? :-)
Thank you for your help!!! :-)
First of all, this is how your html component should look like:
<form [formGroup]="testForm">
<div formArrayName="arrs">
<div class="form-group" *ngFor="let arrItem of testForm.get('arrs').controls; let i = index">
<input type="text" class="form-control" [formControlName]="i">
</div>
</div>
</form>
You do not need to iterate some random count variable inside your html component. You can iterate your added controls.
You may ask "Which controls exactly? They are not added yet!"
Well, this is why you programatically add those controls in ngOnInit:
ngOnInit() {
this.testForm = new FormGroup({
arrs: new FormArray([])
}
);
for (let i = 0; i < this.count; i++) {
const control = new FormControl(null, Validators.required);
(<FormArray>this.testForm.get('arrs')).push(control);
}
this.disableInputs();
}
This is the corrent syntax to initiate the FormArray and then create an initial control inside the for loop and push the newly created control to your array.
Note: there is a disableInputs() function call. This is where you disable your inputs programatically as well:
disableInputs() {
(<FormArray>this.testForm.get('arrs'))
.controls
.forEach(control => {
control.disable();
})
}
A working sample: stackblitz
If you want to enable Dynamic Input enable
form: FormGroup;
orders = [
{ id: 100, name: 'order 1' },
{ id: 200, name: 'order 2' },
{ id: 300, name: 'order 3' },
{ id: 400, name: 'order 4' }
];
constructor(private formBuilder: FormBuilder) {
const controls = this.orders.map(c => new FormControl(''));
this.form = this.formBuilder.group({
orders: new FormArray(controls)
});
this.form.get('orders').controls
.forEach(control => {
control.disable();
})
}
and html should look like this
<form [formGroup]="form" >
<label formArrayName="orders" *ngFor="let order of form.controls.orders.controls; let i = index">
<input type="text" [formControlName]="i">
{{orders[i].name}}
</label>
</form>
Use formArray control in the iteration to assign it in each input:
<form [formGroup]="testForm">
<div formArrayName="arrs">
<div *ngFor="let num of countArr; let idx = index">
<input type="text" [formControlName]="idx" [attr.disabled]="true">
</div>
</div>
</form>
You can refer to this article:
https://angular.io/guide/reactive-forms#display-the-formarray
To disable the FormControls of a FormArray, "reset" makes it easy.
this.formGroupHere.get(['formArrayHere']).reset({
disableFields: {
formControlHere: true,
otherFormControl: true
}
}
);
It's possible by disabling the formControl while initialization or updating as below:
I assume that, testForm is the formGroupName , arrs is the FormArrayName and inputValue is the formControlName
(<FormArray>this.testForm.get('arrs')).push(new FormGroup({
'inputValue': new FormControl({ value: '', disabled: true }, Validators.required),
}));
You have to remember that disabling the form input will not allow you to submit. Instead, you can use the readonly property as below.
<input readonly="readonly" type="text" />
This will help you get the input values from the form as well.
Source for readonly