37

I am trying to access the attributes of a directive in the controller function. However, by the time I access it, it is undefined. I noticed that if I do a simple timer it works. Is there a way to execute code only after the directive and it's scopes are ready and set to be used?

I made a fiddle with it. Make sure your console is open. http://jsfiddle.net/paulocoelho/uKA2L/1/

Here is the code I am using in the fiddle:

<div ng-app="testApp" >
    <testcomponent text="hello!"></testcomponent>
</div>
var module = angular.module('testApp', [])
    .directive('testcomponent', function () {
    return {
        restrict: 'E',
        template: '<div><p>{{text}} This will run fine! </p></div>',
        scope: {
            text: '@text'
        },
        controller: function ($scope, $element) {
            console.log($scope.text); // this will return undefined
            setTimeout(function () {
                console.log($scope.text);    // this will return the actual value...
            }, 1000);
        },
        link: function ($scope, $element, $attrs) {
            console.log($scope.text);
            setTimeout(function () {
                console.log($scope.text);
            }, 1000);
        }
    };
});
edur
  • 1,567
  • 1
  • 11
  • 10
PCoelho
  • 7,850
  • 11
  • 31
  • 36

6 Answers6

29

What works is, if you set

scope.text = $attrs.text;

inside the linking and the controller functions. This will only work initially, as there is no 2way- databinding. You can use $attrs.observe though.

See fiddle: http://jsfiddle.net/JohannesJo/nm3FL/2/

hugo der hungrige
  • 12,382
  • 9
  • 57
  • 84
25

In an isolated scope, a local scope property defined with '@' can not be accessed in the linking function. As @remigio already mentioned, such local scope properties are undefined at that point. $attrs.$observe() or $scope.$watch() must be used to asynchronously obtain the (interpolated) value.

If you are passing a constant value in the attribute, (i.e., no interpolation required, i.e., the attribute's value doesn't contain any {{}}s) there is no need for '@' or $observer or $watch. You can use $attrs.attribute_name once as @hugo suggests, or if you are passing a number or a boolean and you want to get the proper type, you can use $scope.$eval($attrs.attribute_name) once.

If you use '=' to databind a local scope property to a parent scope property, the value of the property will be available in the linking function (no need for $observe or $watch or $eval).

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
10

Instead using $scope to get directive attributes value, personally i prefer using $attrs for the controller function, or just attrs at 3rd parameter of the link function. I have no problem when get the attributes value from a controller by following code without timeout :

var module = angular.module('testApp', [])
    .directive('testcomponent', function () {
    return {
    restrict: 'E',
    template: '<div><p>{{text}} This will run fine! </p></div>',
    scope: {
        text: '@text'
    },
    controller: ['$scope','$attrs', function ($scope, $attrs) {
        console.log($attrs.text); // just call to the $attrs instead $scope and i got the actual value
        $scope.text = $attrs.text; //assign attribute to the scope
    }]
    };
});
Bayu
  • 2,340
  • 1
  • 26
  • 31
9

Since Angular 1.3, you can use bindToController. Here is a sample on how I use it. Here, I add the attribute to scope and then use bindToController to use that inside the controller:

var module = angular.module('testApp', [])
    .directive('testcomponent', function () {
    return {
        restrict: 'E',
        template: '<div><p>{{text}} This will run fine! </p></div>',
        scope: {
            text: '@text'
        },
        controller: function () {
            console.log(this.text);
        },
        controllerAs: 'vm',
        bindToController: true
    };
});

Angular 1.3 introduces a new property to the directive definition object called bindToController, which does exactly what it says. When set to true in a directive with isolated scope that uses controllerAs, the component’s properties are bound to the controller rather than to the scope. That means, Angular makes sure that, when the controller is instantiated, the initial values of the isolated scope bindings are available on this, and future changes are also automatically available.

Neel
  • 9,352
  • 23
  • 87
  • 128
1

The link function is called before the $digest loop, at that moment scope variables are undefined. Look at this chapter and this other to understand how the link function operates. You only use the link function to define watches and/or behaviors for the directive, not to manipulate the model, this is done in controllers.

remigio
  • 4,101
  • 1
  • 26
  • 28
  • 1
    This is not all true. Scope variables are defined by the time the post-link function runs, and it is fine to manipulate your data model in a link function. Controllers are only needed on a directive if you plan to use it to communicate with another directive via the require property. The controller actually runs first, followed by the pre-link, going all the way down the dom tree to the leaves, then post-linking back up to the root. Refer to this thread for clarification. https://stackoverflow.com/questions/24615103/angular-directives-when-and-how-to-use-compile-controller-pre-link-and-post – Kyle Zimmer Mar 08 '18 at 17:10
0

If you are accessing this value from your directive to insert in your view using a directive you can access this attribute using the $compile api and doing something like this

var string = "<div> " + scope.text + "</div>";
$compile(string)(scope, function(cloned, scope){
       element.append(cloned);
});