Sharing data between controller and link functions in Angular directive

Semi-private data common to link and controller without adding to the scope.

Sometimes we would like to access that same data in both controller and link functions in Angular directive. Usually we add this data to the scope object. This has certain problems, for example exposing it to any child scope. Here is an alternative solution: add the shared properties to the controller instance itself, accessible in the link function. In this scenario, you can consider the controller function a typical constructor functions for new controller instance.

Page setup

Let us consider a simple angular application with several instances of directive "foo". I will add a different id attribute to each instance to show they are different.

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<meta charset="utf-8">
<title>Shared data between controller and link function</title>
</head>
<body ng-app="App">
<foo id="1"></foo>
<foo id="2"></foo>
<foo id="3"></foo>
</body>
</html>

The angular directive code is very simple

app
1
2
3
4
5
6
7
8
9
10
11
angular.module('App', [])
.directive('foo', function () {
return {
controller: function ($scope) {
console.log('controller foo');
},
link: function (scope, el, attr) {
console.log('linking foo ' + attr.id);
}
};
});

When run, this prints to the browser console

"controller foo"
"linking foo 1"
"controller foo"
"linking foo 2"
"controller foo"
"linking foo 3"

Let us imagine that we would like to share some data between the controller function and the link function.

Sharing data through $scope

Typically we just add the shared data as a property of the scope, available in the controller function via injected name $scope and in the link function as first argument (named scope by convention).

1
2
3
4
5
6
7
8
9
10
11
12
angular.module('App', [])
.directive('foo', function () {
return {
controller: function ($scope) {
console.log('controller foo');
$scope.shared = 'ok'
},
link: function (scope, el, attr) {
console.log('linking foo ' + attr.id + ' shared ' + scope.shared);
}
};
});

output

"controller foo"
"linking foo 1 shared ok"
"controller foo"
"linking foo 2 shared ok"
"controller foo"
"linking foo 3 shared ok"

You can see this program at shared through scope.

Sharing data through controller instance properties

I do not like polluting the scope object with data that only needs to be shared between controller and link function in the same directive. Properties attached to the scope are available by default in every child scope (unless the child directive limits it). A good solution for sharing is to notice that the controller is a constructor function. It runs for every new instance of the directive, and is available as 4th argument to the link function by default.

1
2
3
4
5
6
7
8
9
10
11
12
angular.module('App', [])
.directive('foo', function () {
return {
controller: function ($scope) {
console.log('controller foo');
this.shared = 'ok'; // add data to the controller instance
},
link: function (scope, el, attr, controller) {
console.log('linking foo ' + attr.id + ' shared ' + controller.shared);
}
};
});

output

"controller foo"
"linking foo 1 shared ok"
"controller foo"
"linking foo 2 shared ok"
"controller foo"
"linking foo 3 shared ok"

The above source version is available here.

Properties on controller visible via require

The sharing via controller instance properties is semi-private. The properties are not available via the scope object, but the controller instances can be injected into child directives using require syntax. See Creating Directives that Communicate.

1
2
3
4
5
6
7
<body ng-app="App">
<foo id="1"></foo>
<foo id="2"></foo>
<foo id="3">
<bar />
</foo>
</body>

Notice the bar directive inside the third foo directive.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
angular.module('App', [])
.directive('foo', function () {
return {
controller: function ($scope) {
console.log('controller foo');
this.shared = 'ok';
},
link: function (scope, el, attr, controller) {
console.log('linking foo ' + attr.id + ' shared ' + controller.shared);
}
};
})
.directive('bar', function () {
return {
require: '^foo',
link: function (scope, el, attr, fooController) {
console.log('bar link can see foo ' + fooController.shared);
}
}
});

output (you can see this version here).

"controller foo"
"linking foo 1 shared ok"
"controller foo"
"linking foo 2 shared ok"
"controller foo"
"bar link can see foo ok"
"linking foo 3 shared ok"

As you see, if a directive requires a controller from a directive up the DOM tree, the 4th argument to the link function becomes the required controller instance. Still I think this is more private than sharing via the scope variable.