3

I am building a new SPA front end to replace an existing enterprise's legacy hodgepodge of systems that are outdated and in need of updating. I am new to angular, and wanted to see if the community could give me some perspective. I'll state my problem, and then ask my question.

I have to generate several series of check boxes based on data from a .js include, with data like this:

$scope.fieldMappings.investmentObjectiveMap = [
  {'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"},
  {'id':"STABLE", 'name':"Moderate"},
  {'id':"BALANCED", 'name':"Moderate Growth"},
   // etc
  {'id':"NONE", 'name':"None"}
];

The checkboxes are created using an ng-repeat, like this:

<div ng-repeat="investmentObjective in fieldMappings.investmentObjectiveMap">
 ...
</div>

However, I needed the values represented by the checkboxes to map to a different model (not just 2-way-bound to the fieldmappings object).

To accomplish this, I created a directive, which accepts a destination array destarray which is eventually mapped to the model. I also know I need to handle some very specific gui controls, such as unchecking "None" if anything else gets checked, or checking "None" if everything else gets unchecked.

Also, "None" won't be an option in every group of checkboxes, so the directive needs to be generic enough to accept a validation function that can fiddle with the checked state of the checkbox group's inputs based on what's already clicked, but smart enough not to break if there is no option called "NONE".

I started to do that by adding an ng-click which invoked a function in the controller, but in looking around Stack Overflow, I read people saying that its bad to put DOM manipulation code inside your controller - it should go in directives. So do I need another directive?

So far: (html):

<input my-checkbox-group
          type="checkbox"
          fieldobj="investmentObjective"
          ng-click="validationfunc()"
          validationfunc="clearOnNone()"
          destarray="investor.investmentObjective" />

Directive code:

.directive("myCheckboxGroup", function () {
  return {
    restrict: "A",
    scope: {
      destarray:      "=",  // the source of all the checkbox values
      fieldobj:       "=",  // the array the values came from
      validationfunc: "&"   // the function to be called for validation (optional)
    },
    link: function (scope, elem, attrs) {
      if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) {
        elem[0].checked = true;
      }
      elem.bind('click', function () {
        var index = scope.destarray.indexOf(scope.fieldobj.id);
        if (elem[0].checked) {
          if (index === -1) {
            scope.destarray.push(scope.fieldobj.id);
          }
        }
        else {
          if (index !== -1) {
            scope.destarray.splice(index, 1);
          }
        }
      });
    }
  };
})

.js controller snippet:

.controller( 'SuitabilityCtrl', ['$scope', function ( $scope ) {
  $scope.clearOnNone = function() {
    // naughty jQuery DOM manipulation code that
    // looks at checkboxes and checks/unchecks as needed
  };

The above code is done and works fine, except the naughty jquery code in clearOnNone(), which is why I wrote this question.

And here is my question: after ALL this, I think to myself - I could be done already if I just manually handled all this GUI logic and validation junk with jQuery written in my controller. At what point does it become foolish to write these complicated directives that future developers will have to puzzle over more than if I had just written jQuery code that 99% of us would understand with a glance? How do other developers draw the line?

I see this all over Stack Overflow. For example, this question seems like it could be answered with a dozen lines of straightforward jQuery, yet he has opted to do it the angular way, with a directive and a partial... it seems like a lot of work for a simple problem.

Specifically, I suppose I would like to know: how SHOULD I be writing the code that checks whether "None" has been selected (if it exists as an option in this group of checkboxes), and then check/uncheck the other boxes accordingly? A more complex directive? I can't believe I'm the only developer that is having to implement code that is more complex than needed just to satisfy an opinionated framework.

1

In my opinion, the quick way to do this in an angular-like way is to introduce a property of your javascript data that tracks the state of the checkbox. Your clearOnNone() function would modify a (dynamically-introduced) checked property of your investmentObjectiveMap. These updates would be immediately be reflected in the DOM. That way there's no jQuery hacking in the midst of your controller.

I forked an old fiddle of mine to give you an example.

html:

<div ng-app="myApp">
    <div ng-controller="MyCtrl">
        <event class="event" ng-repeat="event in events" event="{{ event.name }}"
               ng-class="{'selected' : event.selected}"
               ng-click="selectEvent($index)">
            {{ event.name }}
        </event>
        <br>
        <input ng-repeat="event in events" type="checkbox" ng-model="event.selected">
        <br>
        <button ng-click="selectNone()">select none</button>

    </div>
</div>

js:

var myApp = angular.module('myApp', []).controller('MyCtrl', function ($scope, $http) {
$scope.events = [
    {name: 'event1', selected: false},
    {name: 'event2', selected: false},
    {name: 'event3', selected: false}
];
$scope.selectEvent = function ($index) {
    var e = $scope.events[$index];
    e.selected = !e.selected;
};
$scope.selectNone = function () {
    $scope.events.forEach(function (event) {
        event.selected = false;
    });
};

});

With the ng-true-value and corresponding directives, you can get any value you like into your model when boxes are checked.

EDIT

I just realized that I misunderstood your clearOnNone() behavior, but hopefully you can see how modifying the data and letting angular do the rest is an answer to your question.

| improve this answer | |
0

Your problem seems to be easily solveable with 2-way databinding and some model handler functions in a controller, but you write that you need more than the binding to the fieldmappings object?

Do you actually make use of the fieldmappings object model? If not, you could clone its contents to a model of your own on controller initialization.

After that just set the checkbox models to false instead of your jQuery DOM manipulation and Angular will uncheck the boxes for you.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.