Search code examples
javascriptangularjsangularjs-directiveangularjs-scope

$scope is acting global


I'm trying to learn directives and I have a bunch of directives that look like my code below. But for some reason, if every directive I define has a "$scope.data" or a "$scope.takeInClick", it seems to get the definition from the last directive that was loaded...

So for instance if "$scope.data = 'hello'" in one directive, and "$scope.data = 'hi'"... if the latter directive gets loaded last, but if i console.log($scope.data) in the former directive, it'll show 'hi' instead.

This is pretty troublesome and I'm not sure why this is happening? or if it's intended? I figured that was what $rootScope was for, not $scope. I tried injecting $scope into the directive definition but Angular didn't seem to like this.

Can someone help or explain please thank you

app.directive('someDirective', function (SomeFactory1, SomeFactory2) {

var controller = function ($scope) {

    $scope.data;  //Defined in all of my directives

    function Initialize() {
        $scope.data = {
            stuff: ''
        }
    }

    Initialize();

    $scope.takeInClick = function () {  //Defined in all of my directives too
        //Do Something
    }

};

return {
    templateUrl: 'App_Data/Directives/Something/Something.html',
    controller: controller
}
});

Solution

  • It's a little counterintuitive, but $scope in angularJS is not necessarily isolated. You can define a scope variable in one controller and access it from a different controller at the same level as if it were part of that controller's scope, and that can be both powerful and dangerous. The easiest solution to this is to use controller variables and the controllerAs syntax over $scope whenever possible, but there are many situations where $scope is the right choice, and with practice you'll learn to recognize which is appropriate.

    If you're using $scope, there are a few practices to keep in mind. The first, and probably most important, is to name your variables semantically and specifically. Using general names can cause problems if you have adjacent scopes with similar functions or elements. I've run into scenarios where I was getting an error in one of my directives because another directive on the same page accidentally overwrote a function with the same name and so it was using the wrong data. By being more specific, you are also being more safe, and writing a few extra characters every time you use that variable is a small price to pay for peace of mind and readable code.

    The other solution is to use isolated scopes (search: Isolating the Scope of a Directive). An isolated scope works more closely like you would expect it to, where anything you define at the level of that scope is not accessible outside of the current scope and its children. You have to be careful about which scopes you isolate, though, as any element can only access a single isolate scope. If you have attribute directives which are intended to work together, giving them isolated scopes will result in an error on compilation.

    To add an isolate scope to your code, you simply define it as part of the object, like so:

    app.directive('someDirective', function (SomeFactory1, SomeFactory2) {
    
    var controller = function ($scope) {
    
        $scope.data;  //Defined in all of my directives
    
        function Initialize() {
            $scope.data = {
                stuff: ''
            }
        }
    
        Initialize();
    
        $scope.takeInClick = function () {  //Defined in all of my directives too
            //Do Something
        }
    
    };
    
    return {
        templateUrl: 'App_Data/Directives/Something/Something.html',
        scope: {}, //Scope is now isolated!
        controller: controller
    }
    });
    

    You can also add variables to be injected to your scope through attributes on your directive.

    HTML

    <my-directive lines="parentScopeVariable.lineCount"></my-directive>
    

    JS

    'use strict';
    
    angular
      .module('myModule')
      .directive('myDirective',
        ['$rootScope', function ($rootScope) {
    
        return {
          restrict: 'E',
          scope:
          {
            lines: '=lines'
          },
          templateUrl: 'views/templates/my-directive.html',
          link: function ($scope, element, attributes, controller) {},
          controller: function ($scope)
          {
            //Lines is equal to whatever value the variable outside
            //the scope had, and uses two-way binding like you
            //would expect
            console.log ($scope.lines);
          }
        }]);
    

    Additional Reading: Understanding Scope, Mastering the Scope of the Directives in AngularJS, any number of other blog posts or articles to this effect.

    There's a host of resources to this effect. Don't be afraid to keep looking until you find one that really resonates with your learning style. In my case reading the documentation was like banging my head against a wall, but directives started to make sense after reading a random blog post I found while looking for the answer to a different problem.