Ensuring validation for a single checked checkbox in AngularJS

I'm currently experiencing a challenge with checkboxes in my Angular project. Due to the requirements of the backend, each checkbox must have a unique name, but at least one of them needs to be checked before the form can be submitted.

To address this issue, I created a simple directive with the following code:

{
  restrict: 'A',
  require: ['ngModel', '^form'],
  link: function(scope, el, attrs, controllers) {
    var ngModel = controllers[0];
    var formController = controllers[1];

    scope[attrs.validateOneSelected] =
      scope[attrs.validateOneSelected] || 0;

    ngModel.$validators.oneSelected = function(modelValue, viewValue) {
      if (!viewValue && scope[attrs.validateOneSelected] > 0) {
        scope[attrs.validateOneSelected]--;
      }
      else if (viewValue) {
        scope[attrs.validateOneSelected]++;
      }  

      return scope[attrs.validateOneSelected] > 0;
    };
  }
}

This directive maintains a count of checked checkboxes. It increments when a checkbox is checked and decrements when it is unchecked. The form is considered valid only if more than one checkbox is checked.

While this system works well, I encountered an issue where the validators are triggered only when the model value changes, leaving other checkboxes tagged as invalid.

To address this concern, I thought about using a setTimeout function that would iterate through each $errors.oneSelected and call $validate after validation. However, this approach seems cumbersome to me, and I believe there may be a simpler way to solve this problem.

Edit

Feel free to use this Plunker link to experiment with the solution

Answer №1

I have penned down an insightful response to your query.

Summary: Link to Plunker

This solution is original and pre-dates the provided code snippet, so please pay attention.

If you grasp closures, take a close look at this code:

ANGULAR|JS:

app.controller('MainCtrl', function($scope) {

    var counter = (function(){
        var counterVal = 0;
        return function(checked){
            if(checked === true){
                counterVal++;
                return counterVal;
            } else {
                counterVal--;
                return counterVal;
            }
        }
    })()

    $scope.eval = function(checked){
         $scope.valid = counter(checked);
     }
});

HTML:

<form>
    <input name="first_checkbox" type="checkbox" value="1st" ng-model="st" ng-change="eval(st)"/>
    first
    <input name="second_checkbox" type="checkbox" value="2nd" ng-model="nd" ng-change="eval(nd)"/>
    second
    <input name="third_checkbox" type="checkbox" value="3rd" ng-model="rd" ng-change="eval(rd)"/>
    third
    <input name="forth_checkbox" type="checkbox" value="4th" ng-model="th" ng-change="eval(th)"/>
    forth

    {{counter}}
    <button ng-disabled="!valid">Validate</button>
</form>

Explanation: Closure as described by Mozilla:

Closures are functions that refer to independent (free) variables.

In simpler terms, the function within the closure 'remembers' the environment in which it originated.

In my words: A function that retains its origin and context.

Our approach involves three key steps:

First:

Establishing a closure with a private variable named counter, wherein the closure returns a function that analyzes true or false inputs, modifies the private variable, and provides the updated count.

Second:

Developing a function for each checkbox element to relay the current checkbox state (true or false), updating the $scope.valid variable accordingly.

Third:

Enabling/disabling the button based on $scope.valid.

You can also use console logs to track how the counter increases depending on the checked checkboxes.

Trust this clarifies things! Linial.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transfer information between two clients using AJAX on the same localhost

I'm looking to create a connect four game with the capability of playing online against another player connected to my localhost (within the same WiFi network). While it's possible to play the game on two separate computers, they currently can&ap ...

The size of the Webpack bundle grows with each subsequent build

For my project, I am utilizing webpack to package it as a library. The project consists of a components library, and for each component residing in its own directory under src/ui, I am creating small bundles. Here is an example component structure: src/ ...

Can we trust the accuracy of Function.prototype.toString for our needs?

Can I trust Function.prototype.toString to provide a valid javascript function string for user-defined functions? Do any popular javascript engines differ in how they represent function objects as strings? I came across this question, but it doesn't ...

Inject the JSON data fetched through AJAX into Datatables

I have been successfully using the datatables plugin to populate multiple tables with data. However, I realized that instead of making separate AJAX calls for each table, I could optimize by fetching the data once and storing it in a variable to be used by ...

Which specific file name patterns does npm publish consistently exclude?

When using the npm publish command, the documentation mentions that certain files will not be included in the package unless explicitly added to the "files" list in package.json or un-ignored with a specific rule. What exactly are these "certain patterns"? ...

Harness the power of Highcharts through an Ajax request to retrieve a JSON file

I am having an issue using Highcharts with a JSON file from an external server. When I try to bind the returning file to the chart in my ASP.NET MVC application, it doesn't work. Here is the code I have attempted: http://jsfiddle.net/Q6ngj/2/ jQuery ...

`"Type is invalid" error occurring with component after importing it into a different project``

I am currently working on developing a custom Storybook 7 Typescript component library with React. I have successfully imported this library into another project using a private NPM package. However, one of the components in the library, specifically the ...

How can I stop jQuery from repeatedly appending content every time I click the button?

Hey there, I have a question about calling a JSON array using jQuery. Every time I press the button, it loads the list again instead of multiplying. Here is the JSON array: [{"denumire":"Q Club"},{"denumire":"Carul cu Flori"},{"denumire":"La Rocca"}] And ...

What is the reason for the malfunction of native one-time binding when using the `::` expression in Angular version 1.3.5?

I am having an issue with AngularJS's one-time binding feature using the :: expression. Despite my code setup, the values are still changing. I must be missing something crucial here. Consider this controller: $scope.name = "Some Name"; $scope.chang ...

Managing state changes with a slider in ReactJS

I am currently working with the Foundation slider found at this link: click for doc. To access the slider's current value, they recommend using a hidden input like this: <div class="slider" data-slider data-initial-start="50" data-end="200"> & ...

Utilizing iOS Local Storage for Efficient Form Submission Handling

I feel like my brain is on the verge of exploding. I just can't seem to get this to work as intended, and I'm struggling to pinpoint the issue. Currently, I have a form that needs to be processed using AJAX. Before proceeding with that, I want ...

Error in NW.js file pattern: The package.json file was not found within the source directory file glob patterns

When trying to run a NW.js script located in the /tool/nw.js folder with a source folder set to /dist, an error related to the glob pattern not finding the srcDir (/dist) is thrown. While using glob:false in the configuration resolves the issue, I am curio ...

Despite creating a new array, Vuetify 2 continues to display 10 in the pagination options

I am facing an issue in Vuetify 2 where I have set the pagination options of a data table to be [50,60,70], but on the page, it displays [10,50,60,70]. It seems to be combining the default 10 into the list. https://codepen.io/anon/pen/NQRRzY?&editable ...

What could be causing my if statement to fail even though the condition is met?

I'm attempting to generate dynamic fields based on my chosen attributes. I have two array objects called addAttributes and fakeAttributes. The fakeAttributes contain the details of the selected attributes. I have a dropdown select component that displ ...

Creating a circular image in a responsive navigation bar

Currently, I have a chat navigation bar with divs representing different users, each displaying a photo of the user. My goal is to have these photos displayed as perfect circles. To achieve this, I am using padding-bottom and width properties. However, I a ...

What is the best way to conceal an element in jQuery by utilizing a JavaScript variable?

I have encountered a challenge in concealing this specific page element in the provided html code <table cellspacing=5 cellpadding=3> <tr> <td id="lst2"><h4>Apple Ipad for sale N70,000 (negotiable)</h4></td> & ...

"Is it possible to include a query in a reducer with

Hey, how can I implement this inside a reducer to modify the state's value? doc = { id:"zf123ada123ad", name:"examp", subdoc:{ name:"subdoc examp", subsubdoc:[{ id:"zcgsdf123zaar21", subsubsubdoc:[{ ...

What is the best way to retrieve the information stored in an object?

let items = { 1001: {item: 'Chocolates', price: 10, quantity: 10}, 1002: {item: 'Biscuits', price: 10, quantity: 10}, 1003: {item: 'Bread', price: 20, quantity: 5}, 1004: {item: 'Milk', price: 25, quantity: 5}, 1005: ...

Is there a way to enlarge the font size for a complete tag?

I'm having trouble with a project. One of the tasks is to use jQuery or JavaScript to increase the font size of a paragraph. The console statements are incrementing by +1 with every mouse click (the first click adds 1, the second adds 2, and so on). ...

What causes the namespace to shift when utilizing npm for installing a library?

I've been attempting to integrate whammy.js into a project. The initial line in the source code is window.Whammy = (function(){ yet, after running npm i and inspecting node_modules, I discovered global.Whammy = (function(){ https://github.com/anti ...