CodeSOD: An Angular Watch

Datetime:2016-08-22 23:05:57          Topic: AngularJS           Share

Let’s talk a little bit about front-end development. Even at its best, it’s terrible- decades of kruft mixed with standards and topped off with a pile of frameworks that do their best to turn this mess into a cohesive whole.

Jamesonis suffering through this, and his suffering is the special level of front-end suffering known as “Angular”. Angular bolts Model-View-Controller semantics on top of HTML/JS/CSS, and its big selling point is that it makes two-way data-binding trivially easy.

Under the hood, that two-way data-binding is implemented using a concept of “watchers”. Essentially, these abstract out the event handling and allow Angular- or your own custom code- easily detect changes in the various UI widgets. These watchers also implement nice features, like automatically detecting if a form field is “$pristine” or if the form (or any given field) happens to be “$valid”.

So, for example, if you wanted to have a submit button automatically disable itself if the form were untouched or invalid, you might do something like this:

<button type="submit" ng-disabled="companyProfileForm.$pristine || companyProfileForm.$invalid">Save</button>

Of course, that’s only if you wanted to actually get some benefit out of the ten-thousand line framework you just baked into your application. Jameson’s fellow developers have a very different approach:

var watchers =
        'paymentOptions.PrimaryCreditCardActive,' + //0
        'paymentOptions.PrimaryCardOwnerName,' + //1
        'paymentOptions.PrimaryCardNumber,' + //2
        'paymentOptions.PrimaryCardMonth,' + //3
        'paymentOptions.PrimaryCardYear,' + //4

        'paymentOptions.SecondaryCreditCardActive,' + //5
        'paymentOptions.SecondaryCardOwnerName,' + //6
        'paymentOptions.SecondaryCardNumber,' + //7
        'paymentOptions.SecondaryCardMonth,' + //8
        'paymentOptions.SecondaryCardYear,' + //9

        'paymentOptions.ECheckDirectWithdrawlActive,' + //10
        'paymentOptions.ECheckBankAccountNumber,' + //11
        'paymentOptions.ECheckBankRoutingNumber'; //12

$scope.$watchCollection('[' + watchers + ']', function(newValues){
        $scope.companyProfileForm.$pristine = true;

        if(newValues[0] && newValues[1] && newValues[2] && newValues[3] && newValues[4]){
                $scope.companyProfileForm.$pristine = false;
        }

        if(newValues[5] && newValues[6] && newValues[7] && newValues[8] && newValues[9]){
                $scope.companyProfileForm.$pristine = false;
        }

        if(newValues[10] && newValues[11] && newValues[12]){
                $scope.companyProfileForm.$pristine = false;
        }

        if(newValues[0] && ( !newValues[1] || !newValues[2] || !newValues[3] || !newValues[4])){
                $scope.companyProfileForm.$pristine = true;
        }

        if(newValues[5] && ( !newValues[6] || !newValues[7] || !newValues[8] || !newValues[9])){
                $scope.companyProfileForm.$pristine = true;
        }

        if(newValues[10] && ( !newValues[11] || !newValues[12])){
                $scope.companyProfileForm.$pristine = true;
        }
});

The initial watchers string represents an “Angular expression” which is a fancy way of saying, “JavaScript scope is too complicated, so we’re just not going to do it and require developers to use our own custom expression language”. This string is passed to $watchCollection , which will execute the callback function if the value of any one of those fields changes.

Then, inside of a batch of cryptic if statements, it makes its own decisions about whether or not the Angular-controlled $pristine property should be a certain value or not- decisions that aren’t in any way based on the requirements for this application.

Jameson fixed the code to look something more like my suggested version.





About List