AngularJS: Referencing the parent scope in a directive

von

Recently I wanted to include Dropzone as AngularJS directive in my app. Dropzone lets you upload files to your server with drag and drop. I wrote a tiny CMS with some kind of file manager for images. The page shows several folders, and if you click one Dropzones upload url needs needs to change.

First I wrote a simple HTML-Snippet:

<div class="asset-upload" dropzone="openFolder">
    Upload into: ****
</div>

The new attribute “dropzone” points to a model which holds the reference with the current path to upload. Now let’s look at the dropzone directive. All changes to the model “openFolder” need to be recognized, because in this case the URL has changed.

directives.directive('dropzone', function() {
    return {
        restrict: 'A',
        scope: {
            dropzone: '='
        },
        link: function(scope, element, attrs) {
            scope.$watch('dropzone', function(n, o) {
                console.log("changed url");
            });
        }
    };
});

With the “restrict” property ‘A’ I define the directive can only be applied as an attribute. I also could have made an element of it and deliver all the necessary HTML, but I liked it more that way.

The interesting part comes with the “scope” property. Everything inside the scope object is put in the scope of the directive (surprise). With

dropzone: '='

I put the the model referenced in the HTML attribute into my scope. In other terms, inside my directive I can access a “dropzone” which references the “openFolder”. “openFolder” is an attribute from my parent scope.

With having a new attribute in my scope I am able to create an observer which fires when the value changes. This is being done with the $watch-directive.

The documentation speaks of “setting a bi-directional binding between a local scope property and the parent scope property”. The name of the parent scope property is defined by the value of dropzone=””.

It took me a while to find out how this worked, although it looks very easy. My problem was that my local scope property was named different than the HTML attribute. For example, it looked like this:

// Wrong!! Wrong!! Wrong!!
directives.directive('dropzone', function() {
...
        scope: {
            aaa: '='
        },
...
    };
});

The trick is to name the local scope property exactly like the attribute. If you use “aaa” as local scope property, how should AngularJS know which model you refer too? The definition is stored in some HTML attribute, but there is only dropzone=””.

But if you want to avoid using names of HTML attributes in your local scope you might want to know there is a solution.

directives.directive('dropzone', function() {
...
        scope: {
            aaa: '=dropzone'
        },
...
    };
});

This would map the model defined in the dropzone attribute to the local scope property “aaa”.

Basically you can map all models you like into your local scope. If you have a second attribute which references a models name, just map it like we did above. While this can become very useful I think one needs to be very careful not to rely on to many attributes. This can generate implicit knowledge of the kind “you need to know there must be 5 attributes referencing to $x”. Also you need to know how the tag attributes are named. This can be confusing and your directive isn’t any longer independent from the markup.

A solution could be to create a full-fledged component which brings it’s own HTML with it. But as long as you only need one model which is referenced in the attribute which shares the name with the directive this is just a fine feature.

There are more scope options waiting for you. Please check out the directive guide of AngularJS. Be warned it’s not really easy to understand it.

Tags: #AngularJS #JavaScript