How to two-way bind a checkbox using Angular? - javascript

I currently have an accordion with a bunch of options in the form of checkboxes. The user can select the checkboxes as expected however, I want to already have some of those checkboxes checked depending on certain conditions. The issue is sometimes that is determined after the page has loaded. Below are simplified examples to demonstrate my code (my code is for work and would not be able to share due to confidentiality issues).
My HTML looks like this:
<div class="row">
<div "ngFor="let value of filteredPeople" >
<div>
<input type="checkbox" (click)="selectPeople(value)" [checked]="checkedPeople.get(value)">
{{ value }}
</div>
</div>
</div
My Javascript:
public checkPeople() {
this.selectedPeople.forEach(element => {
this.checkedPeople.set(element, true);
});
}
To explain some variables and methods:
Variables:
filterPeople - a string array of all possible people
checkedPeople - a map with a KVP of string (the people) and boolean (whether or not their checkbox is checked)
selectedPeople - a string array of people whose checkboxes I want already checked
Methods:
selectPeople - checks the corresponding checkbox when user clicks on it
checkPeople - a method called when I want the specific checkboxes checked (these specific checkboxes change based on other factors so I cannot hard code it)
For some reason my checkPeople method does not seem to select the expected checkboxes, I am not sure why but I have a feeling it is to do with the fact that I have used "[checked]" in the HTML and that it should be something else. Can someone clarify the issue for me and help me identify a fix? (Note: as the title suggests, I am using Angular)
Edit:
I have just debugged my code and added breakpoints, the checkedPeople map has the correct mapping of people to true for each of the elements in selectedPeople which shows that the checkPeople method is working as expected. Therefore the issue must be with the [checked] attribute if I'm not mistaken. I cannot see why it wouldn't work though.

You should use [ngModel]="checkedPeople.get(value)"
instead of [checked]="checkedPeople.get(value)"
to initialize the checkbox and
(change)="checkUncheckPeople(value, $event)"
to update the value when you check or uncheck it, where
checkUncheckPeople(value, e) {
this.checkedPeople.set(value, e.target.value);
}
So, in conclusion, your HTML input element will be:
<input
type="checkbox"
[ngModel]="checkedPeople.get(value)"
(change)="checkUncheckPeople(value, $event)"
/>
If you choose to use an object instead of a map then you can also directly use
[(ngModel)]="checkedPeople[value]"
to two-way bind the variable

Related

Angular - trouble with properties updating onchange from checkbox

I am attempting to use a checkbox in Angular in order to toggle the contents of an array. I am using property booleans to determine if 3 different boxes are checked and adjust the contents of the array accordingly. The array defaults to containing all 3 subarrays via declaring this.allNouns in ngOnInit(). When I click the first checkbox, I can see in the console that it is running the associated onchange function and setting the associated boolean to its opposite, but the interpolated string in the view does not change. When I click it again, it will change the view but now everything is one step behind. Also when it hits the console logs, it appears to load the prior length and not the length of the newly established this.allNouns. Below are the html and ts snippets associated with it. This is the first of the three checkboxes, but I plan to apply it to all when smoothed out.
TS
preloadedNouns: Boolean = true;
toggleData() {
if(this.preloadedNouns==false){
this.allNouns=this.nouns.concat(this.onlyUserNouns);
console.log('hitting first condition in toggledata', this.allNouns.length);
}
else {
this.allNouns=this.nouns.concat(this.stockNouns).concat(this.onlyUserNouns);
console.log('hitting second condition in toggledata', this.allNouns.length);
}
}
HTML
<div name="allNouns" [(ngModel)]="allNouns" [ngModelOptions]="{standalone: true}" ngDefaultControl>{{allNouns.length}}</div>
<div class="toggleDiv">
<label>Preloaded Nouns</label>
<input id="preloadedNounsCheck"
type="checkbox"
[checked]="preloadedNouns"
(change)="toggleData(); preloadedNouns = !preloadedNouns">
</div>
i don't clear about your nouns logic.
<div class="toggleDiv">
<label>Preloaded Nouns</label>
<input id="preloadedNounsCheck"
type="checkbox"
[checked]="preloadedNouns"
(change)="preloadedNouns = !preloadedNouns;toggleData()">
</div>
but i hope based on my understanding above code will help to you.
just reorder the function call in (change).

Django Modelmultiplechoicefield Checkboxselectmultiple Problem Getting Selected Checkbox

I have been working all day to try and get the selected values on one set of checkboxes on my Django form and copy the selected ones only to an identical checkbox on my form. If a user clicks on a checkbox in form.author defined below, I want the same identical checkbox to automatically be checked on form.favorite_author defined below.
I've tried a JQuery approach as documented here...Copy Selected Checkbox Value From One Checkbox To Another Immediately Using JQUERY but no luck so far. I've recently begun exploring an avenue with the Modelmultiplechoicefield and the checkboxselectmultiple parameters within the form itself. From what I can tell, when I am using JQuery and trying to check a box on the form, the value is coming back as undefined which is most likely why it is not propagating the checkbox selection to the other checkbox.
Here is my form....
class ManagerUpdateNewAssociateForm(forms.ModelForm):
class Meta:
model = Library
self.fields['author'] = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple(),
queryset=Books.objects.all()
self.fields['favorite_author'] = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple(),
queryset=Books.objects.all()
My HTML...
<div class="spacer83">
{{ form.author }}
</div>
<div class="spacer83">
{{ form.favorite_author }}
</div>
When I tried to trace JQuery, it told me the checkbox selections are undefined. I did read a bit about how Modelmultiplechoicefield, since it uses a queryset it doesn't show the selections, but I can't figure out how to get it to.
Thanks in advance for any thoughts.
In combination with the other issue included in this one, I went back to the JQuery route and explored further. From what I can tell, I was not able to use the class for the input because of the way the HTML for Django forms is generated in this use case. I ultimately was able to leverage the input name and then used the code below to interrogate the checkboxes accordingly:
$(document).ready(function() {
$("[name=author]").change(function() {
let selectedValA = $(this).val();
let isAChecked = $(this).prop("checked");
$(`[name=favorite_author][value="${selectedValA}"]`).prop("checked", isAChecked);
});
});
});

Updating array model deselects iterated <Input> elements

Trying to implement a add/remove sub-item entry; increment/decrement buttons add slots into the array and input fields are added/removed automatically:
<div *ngFor="let item of itemsInNewOrder; let i = index">
<input [(ngModel)]="itemsInNewOrder[i]" xtype="text" name="order" title="order" />
</div>
This is working functionally, but every time a letter is entered into the input, the element is deselected and must be clicked again to enter yet one more letter. How can I solve this?
http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
The link above explains change detection in Angular 2. It also describes some other change detection strategies that you might find useful.
Basically since you did a 2 way binding [(ngModel)]="itemsInNewOrder[i]" to the itemsInNewOrder array, Angular is trying to be helpful and update the view for you.
Each keystroke updates your model (itemsInNewOrder)
Angular detects the update
Angular renders the view again with new form controls
Again, these are new form controls so the user hasn't focused one yet. This is why you have to click into the control after every entered character.
You can fix this by removing your 2 way binding and instead listening to the blur event for each input or implementing something described in the link above.
Blur event listening example:
<input (blur)="blurHandler(item,i)" type="text" name="order" title="order" />
Component
blurHandler(val:string,ndx:number) {
this.itemsInNewOrder[ndx] = val;
}
This might help too
Angular 2 change event - model changes
You can also wrap the values into objects. I don't know why you don't lose focus if you do it like this.
Component:
itemsInNewOrder = [{value: 'Soda'}, {value: 'Burger'}, {value: 'Fries'}];
template:
<div *ngFor="let item of itemsInNewOrder; let i = index">
<input
type="text"
name="order"
title="order"
[(ngModel)]="itemsInNewOrder[i].value"/>
</div>
The simplest answer to this was that I needed to learn and implement Angular 2's reactive forms library, which is part of Angular 2. Using this, I ran into none of difficulties I was experiencing.

Using radio buttons in a ng-repeat loop seems to get the model mixed up

I'm using some radios buttons for a form.
They represent a boolean and they come in pair, one to set the model value at true, and the other at false. Alone, they work perfectly, but the opposite doesn't seems to work :(
Here's a fiddle:
http://jsfiddle.net/leseulsteve/kckjksbb/
controller:
$scope.instructors = [{
isActive: true
}, {
isActive: false
}];
view:
<h2 ng-repeat="instructor in instructors">
<input type="radio" name="status" ng-model="$parent.instructor.isActive" ng-value="true"/> Active
<input type="radio" name="status" ng-model="$parent.instructor.isActive" ng-value="false"/> Inactive
</h2>
I read that it has to do with multiple scope for each repeat iteration, but can't get my head around it
Thank you very much again
Seems to be a combination of factors:
For the radio button groups to operate independently, they need to have different names (for example name="status-{{instructor.id}}")
When written as $parent.instructor.isActive, you are not referring to anything having to do with the instructor object in the loop; you are actually referring to the key instructor on the $parent object. So every iteration of the loop will be pointing at the exact same model. Instead, you can use the instructor object itself in your ng-model attribute: ng-model="instructor.isActive".
I forked and updated the fiddle you linked to: http://jsfiddle.net/0088ab2j/1/
This is actually more of an HTML mistake.
You are assigning all four checkboxes the same name 'status', the browser assumes you want all four checkboxes to work as one.
A simple fix is to change:
name="status"
to
name="{{instructor.id}}"
Like so:
http://jsfiddle.net/2da5tz7o/

Knockout - Checking checkboxes according to observableArray

So I have a list of schedules that I need to display for a user and show which schedules he or she is currently on and give them the possibility to jump on and off said schedules.
My viewmodel looks like this
self = this;
self.shifts = ko.observableArray();
self.selectedShifts = ko.observableArray();
//I populate self.shifts here with a WEB API call
//Run through each shift and check if current user is on it and set checked / not checked value for checkbox
ko.utils.arrayForEach(self.shifts(), function(shift) {
//Clear array
self.usersOnShift([]);
//Populate array with all users on the shift
self.usersOnShift = ko.observableArray(WEB API CALL HERE);
var userInShift = ko.utils.arrayFirst(self.usersOnShift(), function(user) {
if (selectedUserId == user.ID) {
return true;
}
});
if (userInShift) {
self.selectedShifts.push(shift.ID);
}
});
ko.applyBindings(self);
My HTML looks like this
<div class="simple_overlay" id="shiftOverlay">
<div class="details">
<div data-bind="foreach: shifts">
<div><span class="staff-initials" data-bind="text:wardName"> </span><input type="checkbox" data-bind="value: ID, checked: $root.selectedShifts"/> </div>
</div>
<div>
Connect
Close
</div>
</div>
</div>
I can see that the value of the checkboxes are set correctly to the ID of the corresponding shifts. However a shift that I know the user in question is on is not checked and I know that the selectedShifts observableArray contains the value.
Somehow the "checked: $root.selectedShifts" call / check is not working but I know that it contains the right value. What am I doing wrong?
The problem is that your value is an integer, but when bound to the checkbox element, it becomes a string. When the checked binding tries to find the value in the array, it doesn't find a match because it uses strict equality for comparison and (2 === "2") is false.
The simplest way to work around this problem is to convert your values to string when you add them to the array:
self.selectedShifts.push("" + shift.ID);
Of course this means that your model has to change, and that might not be a great solution. I came up with a custom binding, checkedInArray that replaces checked and supports any type of value. You can learn about it, see it in action, and use it like this:
<input type="checkbox" data-bind="checkedInArray: {value: ID, array: $root.selectedShifts }" />
In Knockout 2.3.0 (which is still in development) there will be a new binding, checkedValue, that will allow you use any type of value with the checked binding. Using that version, you could update your HTML to use checkedValue:
<input type="checkbox" data-bind="checkedValue: ID, checked: $root.selectedShifts"/>
Is shift.ID an observable property? If it is, then you need to add it to the array like this:
self.selectedShifts.push(shift.ID());
Otherwise you're just adding the whole observable to the array, and not the value.

Categories

Resources