Angular Material Checkbox doesn´t change state of checked - javascript

I´m working with a list of element and filtering the list using pipes, the filter is multi-selection to filter using more than one value, i save the filter in the localstorage to have persistent in the filter after close the window or reload the page, in the mat-checkbox component i use i function in the [checked]="existInArray(color.id, filterColor)" directive to check the checkbox if the value is already in the filter array to check the checkbox, but i have a problem, if the checkbox is checked using the function, the next time i click the checkbox to uncheck it, the checkbox doesnt change the state of checked true to false, only when i clicked a second time change to checked false
Template
<mat-checkbox *ngFor="let color of filterService.getFilter(filterType.FILTER_COLOR).items.ToArray() | filterQuery:filterOptions.color"
[checked]="existInArray(color.id, filterColor)" class="filter-checkbox" [value]="color.id" [hidden]="color.id === '999999'"
(click)="filterBy(filterType.FILTER_COLOR, color.id, filterColor)">
<div class="assigned">
<div class="assigned-avatar text-center" [ngStyle]="{ 'background-color': color?.color?.bgColor }"></div>
<p class="assigned-name">{{ color.name }}</p>
</div>
</mat-checkbox>
Filter.ts
public existInArray(element, array: Array<string>): boolean {
return array.indexOf(element) > -1;
}
public filterBy(filterType: FilterTypeEnum, element: any, array: Array<string>) {
this.toggleInArray(element, array);
this.updateFilterObservable(filterType);
}
Example, as you can see in the image, the checkbox is already checked by default because i use the existInArray function to check if the value of the check already exist in the filter array, if i try uncheck it, the first time doesn´t work, but when i click a second time the checkbox is unchecked, any idea what could be....?
I think the reason is because the [checked] is trigger before i toggle the element from the array, but no idea so far idea how to solved.... any idea.

This is a timing issue of your [checked]="existInArray(color.id)" being out of sync with your click event.... [checked]="existInArray(color.id)" happens before your click. Subscribe to the change event instead.
(change)="toggleInArray(color.id)"

Top_Select: any[] = []; // selection clear when selected... in your ts file
//on the check_click event firing add this make sure you get the index (index_chkbx) in the event
this.Top_Select[index_chkbx] = true;
in you html

Related

Deselect material-radio-button in Angular 10 on second click [duplicate]

How can I clear <mat-radio-button> selected on second click
(after it is already selected)
I know I can implement this behavior with checkbox but i need to allow select only one item.
Any help?
My code look like :
<mat-radio-group name="WasFamilyMemberPresent">
<mat-radio-button *ngFor="let item of lookupNoYes" [value]="item.Code" >
{{item.Desc}}
</mat-radio-button>
</mat-radio-group>
** UPDATED ** 10/11/2022
reference Sara's stackblitz answer below.
You can do the following to accomplish this.
Assign a template reference to the button #button and pass it to the component method (click)="checkState(button)"
<mat-radio-button #button class="example-radio-button" *ngFor="let season of seasons" [value]="season" (click)="checkState(button)">
Create a local variable in the component to store the button value for comparison in checkState()
currentCheckedValue = null;
DI Renderer2 to remove focus from the button
constructor(private ren: Renderer2) { }
Wrap logic in setTimeout to put it on the digest cycle, check if local variable exist and if current value equals argument value... if true deselect, remove focus and null local variable... else set local variable for future comparison.
checkState(el) {
setTimeout(() => {
if (this.currentCheckedValue && this.currentCheckedValue === el.value) {
el.checked = false;
this.ren.removeClass(el['_elementRef'].nativeElement, 'cdk-focused');
this.ren.removeClass(el['_elementRef'].nativeElement, 'cdk-program-focused');
this.currentCheckedValue = null;
this.favoriteSeason = null;
} else {
this.currentCheckedValue = el.value
}
})
}
Stackblitz
https://stackblitz.com/edit/angular-c37tsw?embed=1&file=app/radio-ng-model-example.ts
I have simplified Marshal's code here
https://stackblitz.com/edit/angular-c37tsw-zho1oe
I removed setTimeout and Renderer2 import and it would seem to work even without these but with an event.preventDefault().
I'm not sure what answer you are looking for since the angular material radio buttons default behaviour is to only allow one selected item. (This is what i assume you are asking based off of your question)
Are you trying to create behaviour where the user can deselect all radio buttons?
I had the same problem as OP and I tried both the most popular and second most popular answers but neither of them worked for me. I'm using Angular 10 and this post is 4 years old so I hope this answer will help someone else who has my problem.
I noticed that the mat-radio-button was being assigned a class 'mat-radio-checked' when you checked it. The reason I believe that the above 2 answers no longer work is that the value of the button is set before you receive it in the ts file so no matter how you try to assign the _checked value to false it isn't going to work. But changing that class is doable. This method also allows you to deselect the button without having to keep track of any kind of global variable. Here's a generic example:
In the html assign each button a reference variable using the # and an on click event passing the event and button reference.
<mat-radio-group class="radio-group">
<mat-radio-button #option1 class="radio-item" value="New User" (click)="onRadioClick($event, option1)”>New User</mat-radio-button>
<mat-radio-button #option2 class="radio-item" value=“Existing User" (click)="onRadioClick($event, option2)”>Existing User</mat-radio-button>
</mat-radio-group>
Then in the onRadioClick function check to make sure that the button the user clicked has a class 'mat-radio-checked'; if it does then remove the check class and add our own flag marked-unchecked. This way if you uncheck an option then check it again we will know that it has been checked again and can go ahead and add the 'mat-radio-checked' class again.
onRadioClick(event: any, button: any): void {
let targetButton = event.target;
while (targetButton && targetButton.parentNode && targetButton.parentNode.classList) {
targetButton = targetButton.parentNode;
if (targetButton.classList.contains('mat-radio-checked') && !targetButton.classList.contains('marked-unchecked')) {
targetButton.classList.remove('mat-radio-checked');
targetButton.classList.add('marked-unchecked');
break;
}
if (targetButton.classList.contains('marked-unchecked')) {
targetButton.classList.remove('marked-unchecked');
targetButton.classList.add('mat-radio-checked');
}
}
}
For further explanation on this just inspect your mat-radio-button element in your browser and you'll be able to see the mat-radio-checked css class I was talking about and it should become apparent.

Enable Two-Way data binding on Angular 6 for input type checkbox inside *ngFor

Problem: Two-way data binding for checkbox in *ngFor directive
I have a template driven form in which I am using *ngFor to loop over few radio buttons. Based on one key, I show checkbox next to radio buttons in disabled state on page load. If user clicks on a radio button and if it has a corresponding checkbox, I enable its state. I can then click on checkbox.
However, I want to be able to disable previous checkbox and reset its value when user clicks on another radio button and thus pass the correct form values. (I show them as json in beloe code demo url)
Demo code example: https://stackblitz.com/edit/angular-ba8pfz
If you see the demo code, I should be able to reset the checkbox if I toggle between "Marvel Heroes" and "Dc Heroes"
Appreciate your time and suggestions.
You cannot simply set the checked attribute to false as that sets the content checked attribute to an empty string - which is the equivalent of true or checked. Hence the check mark goes away, but the Boolean state of the control is not changed. Have to change the Boolean state of the control. One simple way to do that is to generate a click or change event on the control.
Try this:
enableRecursive(event, recursiveChk: boolean) {
let clickEvent = new MouseEvent('click');
this.recursiveChk.map((elem) => {
if (elem.nativeElement.checked) {
elem.nativeElement.dispatchEvent(clickEvent);
}
elem.nativeElement.disabled = true;
if (`recursive_${event.target.id}` === elem.nativeElement.id) {
elem.nativeElement.disabled = false;
}
});
}

Validate checkbox before calling ng-change

I have a repeater:
<tr data-ng-repeat="worktime in workTimesFiltered ">
Inside the repeater I have an input checkbox:
<input ng-change="updateIncludeOnInvoice(worktime.includeOnInvoice, worktime.timeType)" name='obj1_data' type="checkbox" ng-model="worktime.includeOnInvoice" ng-true-value="true" ng-false-value="false">
When it's checked I call updateIncludeOnInvoice. The problem is that I don't want it to be clickable if another worktime.timeType == "0%".
I can capture this in updateIncludeOnInvoice and have tried setting worktime.includeOnInvoice = false, but the checkbox remains on screen.
I have found a way to get access to the dom element that was clicked to manually set it back to unchecked, but it seems hacky....
What's the correct way of dealing with this?
Validate checkbox somehow before calling ng-change, if so how?
If I understand this you just want to disable the checkbox when some condition is met.
<input ng-disabled="isConditionMet()">
$scope.isConditionMet = function() {
// check your condition and return true/false
};

Jquery click event with binded event

Hi guys I am having a problem with Events. I have a checkbox list and I have a main check box that checks all boxes. When I clickEvent some of my checkbox list items it should add data-id attr to the "selected obj". So in my case when I press main check box to check all others every thing is ok (it simply clicks all other elements). but when i do that it empties my array. I mean if i uncheck it will be the way it supposed to be but checked (when uncheck it fills when i check it empties).
......
var selected = {};
var reload = function(){
selected = {};
$('.checkbox_all').unbind('click');
$('.table_checkbox').unbind('click');
$('.checkbox_all').bind('click', checkAll);
$('.table_checkbox').bind('click', checkMe);
}
var checkMe = function(e){
var checkbox = $(e.target);
var id = checkbox.attr('data-id');
//console.log(id);
if(checkbox.attr('checked')){
selected[id] = id;
}else{
if(selected[id]) delete selected[id];
}
console.log(selected);
}
var checkAll = function(e){
if($(e.target).attr('checked')){
$('.table_checkbox').each(function(){
if($(this).attr('checked') === false){
$(this).click();
}
});
}else{
$('.table_checkbox').each(function(){
if($(this).attr('checked') === true){
$(this).click();
}
});
}
//console.log(selected);
}
.......
HTML:
<tr><th class="table-header-check"><input type="checkbox" class="checkbox_all"/></th></tr>
<tr class=""><td><input type="checkbox" data-id="5" class="table_checkbox"></td></tr>
<tr class="alternate-row"><td><input type="checkbox" data-id="6" class="table_checkbox"</td></tr>
<tr class="alternate-row"><td><input type="checkbox" data-id="8"
....ETC\
My problem is that when i click .checkbox_all it should click on all .table_checkbox(that r cheched or uncheched)... it just clicks all checkboxes like a main checkbox... it works fine, but i have an event all other checkboxes if i click em i add some data to array when i unclick em it removes data from array.... so when im clicking checkboxes sepperatly they add /remove data to array properly... but when im clicking on main checkbox... it clicks on right checkboxes but the data array is empty when all checked and full when all unchecked... it must be the opposite way
Could you instead go for a cleaner solution, and generate selected on the fly? See here for an example (and a JSFiddle for everyone else): http://jsfiddle.net/turiyag/3AZ9C/
function selected() {
var ret = {};
$.each($(".table_checkbox"),function(index,checkbox) {
if($(checkbox).prop("checked")) {
ret[$(checkbox).prop("id")] = true;
}
});
return ret;
}
** EDIT: **
If you're looking to have an array that is added to and removed from, then this JSFiddle (http://jsfiddle.net/turiyag/pubGb/) will do the trick. Note that I use prop() instead of attr(), in most cases, especially this one, you should use prop() to get the value you want.
To work with your own code you need to understand the order of events. When you programmatically call click() on the checkbox the javascript (checkMe() for children) executes before the state of each child checkbox is changed (e.g., adding attribute 'checked'). It is because of this reason that the checkMe() function was adding and removing ids in the selected array in the reverse order. You can confirm this by adding the following debug line in the checkMe function:
console.log('Checked state of checkbox id:' + id + ' is: ' + checkbox.prop('checked'));
Case1: Clicking checkAll when it is Unchecked; it calls checkMe() for each child checkbox but finds the 'checked' attribute as undefined. So it executes the delete code. After executing checkMe the 'checked' attribute is added on the checkbox.
Case2: Clicking checkAll when it is Checked; the checkMe() function finds the 'checked' attribute previously added and fills the array. Later an event is probably fired to remove the 'checked' attribute.
I changed the following lines to quickly test this and seems to be working:
Bind checkMe on change event instead of click in reload function:
$('.table_checkbox').bind('change', checkMe);
Change the condition for unchecked children in checkAll function when the .checkbox_all is checked:
if($(this).prop('checked') === false) {/*call child click*/}
//Use prop instead of attr because it takes care of 'undefined' cases as well. If you want to keep using attr because you're on an older version of jquery then add something like:
typeof $(this).attr('checked') == 'undefined'
and also the condition when .checkbox_all is unchecked:
if($(this).prop('checked') === true) {/*call child click*/}
Hope this helps. Here's a jsbin to play with..

How to trigger an event ONLY if both dropdowns are selected?

I have a dropdown select list on my page of class="TypeFilter".
I have a jQuery event that fires when a value in that list is selected.
$(".TypeFilter").change(function()
{
// Extract value from TypeFilter and update page accordingly
));
I now have to add another list to the page, and I want to implement functionality which will prevent the .change(function() from running unless both are selected.
In both lists the first option in the list is some text instructing the user to select one of the items, so I was thinking of just writing some logic to test that both lists have a selected index greater than 0.
I think this is a touch unclean though, especially considering that other pages that have a TypeFilter use the same logic.
Is there any nifty functionality in jQuery that can do this?
edit I should specify that the user needs to be able to update the page by selecting either dropdown, so I can't put the onchange on the second element and test that the first element has a selected value, as suggested in one of the answers
If you bind the same event to all dropdowns, you can get a collection of all the dropdowns and check that all of them are selected. Example:
$('.Dropdown').change(function(){
var elements = $('.Dropdown');
if (
elements.filter(function(){
return this.selectedIndex > 0;
}).length == elements.length
) {
// all dropdowns are selected
}
});
As you partly mention, put the onchange on the second element and test that the first element has a selected value before you fire off any logic.
Use bind instead, and as the eventdata, send a function that checks that either that both are selected or that the other is selected. Untested code:
function checker() {
// test your conditions
}
$(".TypeFilter").bind('change', {test: checker}, function(event)
{
if (event.data.test && event.data.test()) {
// Extract value from TypeFilter and update page accordingly
}
));
This way the other pages that use the same function will not notice any changes.

Categories