Last Updated: February 25, 2016
·
3.218K
· joseym

Cheat AngularJs Isolate Scope

My last protip was really just a preface for this one. I shameless plug myself.

I was working on a hotkey service in angular, and wanted to create a hotkey to focus on the global search bar in an app i'm working on (/).

The problem I ran into was the two way binding. The hotkey would update the scoped boolean value but not consistently.

I managed to get that to work by applying an isolate scope, but we all know what kind of headaches THOSE can cause.

So the directive now had its own scope, but the value assigned to the search field wasn't of this new scope, it exists in the $parent - le-sigh.

So here's my focus directive, then I'll explain what's going on.

FYI - There are no references to the hotkey magic, this is just a directive to focus a specified field.
That takes place elsewhere and isn't directly bound with this.

var focusDirective = angular.module('jmFocus', []);
focusDirective.directive('fieldFocus', function ($timeout, $parse) {

  return {
    restrict: 'A',
    scope: {
      hasFocus: '=fieldFocus'
    },
    link: function (scope, elem, attrs) {

      var model = $parse(attrs.ngModel);

      scope.$watch(model, function(n, o){
        attrs.ngModel.toObject(scope.$parent, n);
      })

      scope.$watch('hasFocus', function(n, o){
        if(n) {
          $timeout(function(){ 
            elem.focus();
          })
        }

      });

      elem.bind('blur', function() {
        scope.hasFocus = false;
      });

    }

  };
})

As you may be able to tell from the above, the input would have an attribute field-focus, which also declares the directive on that field.

The value of that attribute should be a scope assignment: $scope.focusSearch = false.

At any point, if $scope.focusSearch is changed to true then the search field should be focused.

I require the search field to have an angular model (which I watch and perform the search based on this value)

So the input would look something like this:

<input type="text" ng-model="query.value" field-focus="focusSearch" placeholder="search for stuffs" />

So, looking back at the directive:

Preparing the model

var model = $parse(attrs.ngModel);

What this does is get the model attribute ready to be handled, like in a proper angular scope.

This essentially creates a one-way binding.

Updating the parent model (outside isolate scope)

scope.$watch(model, function(n, o){
  attrs.ngModel.toObject(scope.$parent, n);
})

n is the new value returned from model.

Here we're taking our prepared model and watching it for changes.

I'm then using the toObject method I referenced earlier to update the parent scope with the passed value.

Conclusion

You could easily achieve this without the toObject method if the parent scope item was one level deep, but if the value of ng-model (or whatever the attribute you're tracking) is a nested object you'd need to be able to populate an object at the required level.

I hope someone finds this useful.