Jer
Jer

Reputation: 5648

Getting data from a Controller/Service into a Directive?

I'm a relative beginner with AngularJS and am writing an app that will have various reusable components (e.g. the same table may appear 5 times on the screen, with slightly different columns - I can go into more detail if necessary). Other people will want to write code which uses these components as well. Similarly, multiple components will want to pull data from the same sources, and multiple people will want to make the same REST calls to get data.

The general architecture I have in mind:

For a simple example, a directive would print a list of names using ng-repeat. A service would have a "public" function getNames() which returns a list of names. I'm writing an app - I use the "canned" directive and service, I write a controller that has the service as a dependency, call the getNames() function and get the results to the directive. My HTML page simply uses the directive like any other HTML element.

My main question - what's the best way to get the data into the directive? I know there are many options as far as scope goes, but it just seems wrong to have to create a variable with a certain name within my control and have it "magically" work.

On a broader level, is this a reasonable approach? I'm essentially seeing the directives and services as the public API that developers would use.

Upvotes: 0

Views: 2869

Answers (1)

pje
pje

Reputation: 2468

It sounds like what you're looking for is called isolate scope. An isolate scope is defined inside the directive, and the variables in the isolate scope are assigned as attributes when using the directive in a view.

For instance, consider the following directive:

myApp.directive('myTable', function () {
    return {
        restrict: 'E',
        replace: true,
        templateUrl: 'MyTable.html',
        scope: {
            foo: '=items'
        }
    };
});

This directive has a couple properties on it. Here I'm just going to talk about the scope property, but you can read about the rest (and directives in general) here.

The scope property defines an isolate scope. The isolate scope allows you to map values from an external controller's scope, allowing you to bind to them without worrying about what actually exists in the external scope. This is a layer of abstraction that allows your directives to be more flexible and reusable.

Each property in the scope object defines a mapping between some internal property on the isolate scope and some external controller's scope object. In the above example, we have a property called foo. The right side of the property definition, seen as "=items", is declaring the variable mapping. This is effectively saying, "when instantiating this directive, look for an attribute named "items" on the html element and assign its value to the property named "foo" on the isolate scope.

So, for instance, if our directive's template (specified by the templateUrl property above) is the following:

// MyTable.html

<table style="width:300px">
  <tr>
    <td ng-repeat="item in foo">{{item}}</td>
  </tr>
</table>

Then we can use this directive in our main html page like so:

<div ng-controller="SomeGenericController">
  <my-table items="ourItems" />
</div>

Notice above, we have added the items attribute to our directive, and assigned it the value of ourItems. Now, one clarification here; when we assign items="ourItems", we must have some property on our controller's scope named ourItems. So, for instance, maybe our controller looks like this:

myApp.controller('SomeGenericController', ['$scope', function ($scope) {
  $scope.ourItems = ['ball', 'shovel', 'towel'];
});

The above usage will display a table with the items from the SomeGenericController's items array.

Using this pattern, we can assign any old array to use this table directive. Say we have some other controller that has an array of items we want to put in a table:

myApp.controller('AccountsController', ['$scope', function ($scope) {
  $scope.names = ['john', 'jill', 'ted'];
  ...
  // some more stuff down here specific to this controller
});

Now, we can use our table directive along with this controller by mapping the names array to our isolate scope's foo property by using the items attribute:

<div ng-controller="AccountsController">
  <my-table items="names" />
</div>

So, that's the general idea behind creating reusable directives. There are some tricks we can use to make this cleaner. For instance, if the name of our attribute mapping and the name of the isolate scope property are the same, we can use the shorthand = instead of =items. There are also a few other operators we can use when defining mapping, namely @ and &, but I'll let you look those up on your own :) You can also define controllers that are internally contained and specified in the directives definition, and again that's something I'll leave to you to learn more about :)

Anyway, hope this helps!

Upvotes: 5

Related Questions