Feng erdong's Blog

Life is beautiful

Testing Directive - Call Controller Method in Default Scope

| Comments

Call controller method in default scope

1
2
3
4
5
6
7
8
9
<div ng-controller="FruitController" ng-init="init()">
    <h4>Call controller method in default scope</h4>
    <label>What's your favorite fruit(name can only contains letter)</label>
    <input type="text" ng-model="newFruit"/>
    <button type="button" add-fruit-method>validate and add</button>
    <ul>
        <li ng-repeat="fruit in fruits track by $index"></li>
    </ul>
</div>

This time, we want to validate the fruit name use typed in first, if it only contains letter, then it’s eligible to add in, otherwise nothing happen. The validation logic is defined in controller, we need to test the validation method is called inside our directive.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
describe('directives', function () {
    beforeEach(module('myApp.directives'));

    describe('addFruitMethod', function () {
        var $scope, element;
        beforeEach(inject(function ($rootScope, $compile) {
            $scope = $rootScope;
            $scope.fruits = [];
            $scope.newFruit = 'apple';
            $scope.isValid = angular.noop;

            element = angular.element('<input type="text" name="fruit" id="fruitDefault" ng-model="newFruit"/><button type="button" add-fruit-method>validate and add</button>');
            $compile(element)($scope);
        }));

        it('should add valid fruit to fruit list when click button', function () {
            var isValid = spyOn($scope, 'isValid').andReturn(true);

            element.filter('button').trigger('click');

            expect(isValid).toHaveBeenCalled();
            expect($scope.fruits[0]).toBe('apple');

        });

        it('should reject invalid fruit when click button', function () {
            var isValid = spyOn($scope, 'isValid').andReturn(false);

            element.filter('button').trigger('click');

            expect(isValid).toHaveBeenCalled();
            expect($scope.fruits.length).toBe(0);
        });
    });
});

It’s very similar with testing manipulate data on default scope, the only thing different is we spy on the validation method to verify it has been called, also we let the spy object return the corresponding result to execute each branch.

Testing Directive - Model Manipulation With Isolated Scope

| Comments

Model manipulation with isolated scope

1
2
3
4
5
6
7
8
9
<div ng-controller="FruitController" ng-init="init()">
    <h4>Model manipulation with isolated scope</h4>
    <label>What's your favorite fruit</label>
    <input type="text" ng-model="newFruit"/>
    <button type="button" add-fruit fruits="fruits" new-fruit="newFruit">Add</button>
    <ul>
        <li ng-repeat="fruit in fruits track by $index"></li>
    </ul>
</div>

The above directive manipulate data in default scope, now we create a isolated scope for the directive, the test point change to verify the data on isolated scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
describe('directives', function () {
    beforeEach(module('myApp.directives'));

    describe('addFruit', function () {
        var $scope, element;
        beforeEach(inject(function ($rootScope, $compile) {
            $scope = $rootScope;
            $scope.fruits = [];
            $scope.newFruit = 'apple';
            element = angular.element('<input type="text" ng-model="newFruit"/><button type="button" add-fruit fruits="fruits" new-fruit="newFruit">Add</button>');

            $compile(element)($rootScope);
        }));
        it('should add fruit to fruit list when click button', function () {
            element.filter('button').trigger('click');

            expect(element.scope().fruits[0]).toBe('apple'); // use element.scope() to access isolated scope
            expect($scope.fruits[0]).toBe('apple'); // also verify default scope is updated
        })
    });
});

In the test, we setup the surrounding scope, then verify both the default scope and isolated scope are updated.(we use element.scope() to access the isolated scope).

Testing Directive - Model Manipulation With Default Scope

| Comments

Model manipulation with default scope

1
2
3
4
5
6
7
8
9
<div ng-controller="FruitController" ng-init="init()">
    <h4>Model manipulation with default scope</h4>
    <label>What's your favorite fruit</label>
    <input type="text" ng-model="newFruit"/>
    <button type="button" add-fruit-default>Add</button>
    <ul>
        <li ng-repeat="fruit in fruits track by $index"></li>
    </ul>
</div>

Let’s try something advanced, we have a input box to type in a fruit name and a button, after click the button, the value of the input box will added into the fruit list.

How to write test for this directive? If the fruit list on the scope will have the fruit we passed in after we click the button, then we think it’s working well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
describe('directives', function () {
    beforeEach(module('myApp.directives'));

    describe('addFruitDefault', function () {
        var $scope, element;
        beforeEach(inject(function ($rootScope, $compile) {
            $scope = $rootScope;
            $scope.fruits = [];
            $scope.newFruit = 'apple';

            element = angular.element('<input type="text" name="fruit" id="fruitDefault" ng-model="newFruit"/><button type="button" add-fruit-default>Add</button>');
            $compile(element)($scope);
        }));

        it('should add fruit to fruit list when click button', function () {
            element.filter('button').trigger('click');

            expect($scope.fruits[0]).toBe('apple');
        });
    });
});

Not like the first test code, this one needs to verify the data on scope, we need a real scope, also we need to initialize the state of the scope, after we compile the html fragment using the scope, interact with the DOM will affect the scope.

Testing Directive - Basic Dom Manipulation

| Comments

Basic Dom Manipulation

1
2
3
4
5
<div>
    <h4>Basic Dom Manipulation</h4>
    <input type="text" name="search" id="search"/>
    <button type="button" clear-search>Clear</button>
</div>

We’re going to write a directive for the button, after click it, the search box wil be clear. To demonstrate DOM manipulation, we’ll not set ngModel for the search box. Let’s write out the test first.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
describe('directives', function () {
    beforeEach(module('myApp.directives'));

    describe('clearSearch', function () {
        var $scope, element;
        beforeEach(inject(function ($rootScope, $compile) {
            $scope = $rootScope;
            element = angular.element('<input type="text"/><button type="button" clear-search>Clear</button>');

            $compile(element)($scope);
        }));
        it('should clear search box when click clear button', inject(function ($rootScope, $compile) {
            var val = spyOn(jQuery.fn, 'val');
            var prev = spyOn(jQuery.fn, 'prev');

            element.filter('input').val('some value');
            element.filter('button').trigger('click');

            expect(val).toHaveBeenCalled();
            expect(val.mostRecentCall.args[0]).toBe('');
        }));
    });
});

To be honest, directive which only manipulate DOM doesn’t need scope, so we can remove scope in test, the test still can pass.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
describe('directives', function () {
    beforeEach(module('myApp.directives'));

    describe('clearSearch', function () {
        var element;
        beforeEach(inject(function ($rootScope, $compile) {
            element = angular.element('<input type="text"/><button type="button" clear-search>Clear</button>');

            $compile(element)({});
        }));
        it('should clear search box when click clear button', inject(function ($rootScope, $compile) {
            var val = spyOn(jQuery.fn, 'val');
            var prev = spyOn(jQuery.fn, 'prev');

            element.filter('input').val('some value');
            element.filter('button').trigger('click');

            expect(val).toHaveBeenCalled();
            expect(val.mostRecentCall.args[0]).toBe('');
        }));
    });
});

Hg Cheat Sheet

| Comments

  • Remove unversioned file
    hg purge
  • Discard local change
    hg revert [FILE]
  • Rollback last commit
    hg rollback
  • Looking for changes in the remote repository
    hg incoming
  • Looking for changes you’ve made that aren’t in the remote repository
    hg outgoing
  • Reset to a given version (reset --hard in git)
    hg strip -r commit_hash

branch

  • Switch to another branch
    hg update [BRANCH_NAME]
  • Show current branch name
    hg branch
  • Merge changes from another branch to current branch
    hg merge [ANOTHER_BRANCH_NAME]
  • Check logs on a given branch
    hg log --only-branch my_branch

Testing With Angular – Directive

| Comments

As a Java developer, I am familiar with test driven development with Java language, but for angular, I’m not, sometime it’s even hard to write angular test after the implementation is done. So I’ll write a series of articles about how to test with angular, basically, I’d like to include the test strategy for directives, controllers and services, this article will begin with how to test angular directive.

Let’s check out the below examples form easy to hard.

Scope in Angular

| Comments

This post is copied from stackvoerflow, check this for more details.

After writing a lot of directives, I’ve decided to use less isolated scope. Even though it is cool and you encapsulate the data and be sure not to leak data to the parent scope, it severely limits the amount of directives you can use together. So,

Isolated: a private sandbox

If the directive you’re going to write is going to behave entirely on its own and you are not going to share it with other directives, go for isolated scope. (like a component you can just plug it in, with not much customization for the end developer) (it gets very trickier when you try to write sub-elements which have directives within)

None: simple, read-only directives

If the directive you’re going to write is going to just make dom manipulations which has needs no internal state of scope, or explicit scope alterations (mostly very simple things); go for no new scope. (such as ngShow,ngMouseHover, ngClick, ngRepeat)

Child: a subsection of content

If the directive you’re going to write needs to change some elements in parent scope, but also needs to handle some internal state, go for new child scope. (such as ngController)

So keep in mind that don’t use isolated scope unless you have to.

Traps in Angular Directive – Return False

| Comments

Return false will prevent default browser behavior

I was blocked for whole afternoon by a very weird phenomenon, two radio buttons(with the same name), one of them can never be checked after you click it, at the last, I found out the root cause is the browser’s default behavior is prevented by the return false statement in directive.

Let’s see what a normal radio button group should be:

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>
    <div>
        <div>
            <input type="text" name="" id="">
        </div>
        <div>
            <input type="radio" name="gender" id="female">
            <label for="female">Female</label>
            <input type="radio" name="gender" id="male">
            <label for="male">Male</label>
        </div>
    </div>
</body>

check the demo, click a radio button can make it checked.

Now let’s make some trouble, if we click a text input field, we want to show a console log say ‘input field clicked’, if other type input component is clicked, do nothing. let’s write a directive to handle this.

1
2
3
4
5
6
7
8
9
10
11
12
13
<body ng-app="Demo" ng-controller="DemoController">
    <div tell-me>
        <div>
            <input type="text" name="" id="">
        </div>
        <div>
            <input type="radio" name="gender" id="female">
            <label for="female">Female</label>
            <input type="radio" name="gender" id="male">
            <label for="male">Male</label>
        </div>
    </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
angular.module('Demo', [])
    .controller('DemoController', ['$scope',
        function ($scope) {}
    ])
    .directive('tellMe', [
        function () {
            return {
                link: function (scope, element, attr) {
                    element.bind('click', function () {
                        var target = angular.element(event.target);
                        if (!target.is(':text')) {
                            return false;
                        } else {
                            console.log('input field clicked');
                        }
                    });
                }
            };
        }
    ]);

Check the demo, you’ll see the radion button group is not functional well, one of them can never be checked, this is all because we use return false in directive.

After we replace return false with return, everything back to normal, check again here

Traps in Angular Directive – Isolated Scope

| Comments

I was trapped in angular directive this work, after struggled for hours, I noticed below traps in angular directive.

Directive with isolated scope will impact native angular directive

If your own directive has a isolated scope, then it will impact native angular directive, which means, sometime, ngModel, ngDisabled suddenly doesn’t work, because they’re impacted by your directive. take below as an example:

We have a input field to type in a programming language, click the ‘Add’ button will add it into a list(as it’s a simple demo, so data validation is not concerned)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body ng-app="DemoApp">
    <div ng-controller="DemoController">
        What's your favorite programming language (up to five):
        <input type="search" ng-model="profile.newLanguage" />
        <input type="button"
          value="Add"
          add-language
          languages="profile.languages"
          new-language="profile.newLanguage" />
        <div>
            <ul>
                <li ng-repeat="language in profile.languages"></li>
            </ul>
        </div>
    </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
angular.module('DemoApp', [])
    .controller('DemoController', ["$scope",
        function ($scope) {
            $scope.profile = {};
            $scope.profile.languages = [];
        }
    ])
    .directive('addLanguage', [
        function () {
            return {
                scope: {
                    languages: '=',
                    newLanguage: '='
                },
                link: function (scope, ele, attr) {
                    ele.on('click', function () {
                        scope.languages.push(scope.newLanguage);
                        scope.$apply();
                    });
                }
            };
        }
    ]);

we put a directive addLanuage on the button, which will get the value in the input field and add it to language list, due to we need to operate the language list, so we use a isolated scope to access it inside the directive.

Try it yourself. demo

Now the new requirement comes, a user only allow to fill up to five programming languages, we need to disable the Add button after user have input 5 languages.

Seems a small change will fit the new requirement, ngDisabled should solve this.

1
2
3
4
5
6
7
<input
  type="button"
  value="Add"
  add-language
  ngDisabled="reachThreshold()"
  languages="profile.languages"
  new-language="profile.newLanguage" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
angular.module('DemoApp', [])
    .controller('DemoController', ["$scope",
        function ($scope) {
            $scope.profile = {};
            $scope.profile.languages = [];
            $scope.reachThreshold = function () {
                return $scope.profile.languages.length === 5;
            };
        }
    ])
    .directive('addLanguage', [
        function () {
            return {
                scope: {
                    languages: '=',
                    newLanguage: '='
                },
                link: function (scope, ele, attr) {
                    ele.on('click', function () {
                        scope.languages.push(scope.newLanguage);
                        scope.$apply();
                    });
                }
            };
        }
    ]);

Play with the updated code you’ll find out ngDisabled is not working! demo

What can we do to save the ngDisabled damaged by isolated scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body ng-app="DemoApp">
    <div ng-controller="DemoController">
        Programming language:
        <input type="search" ng-model="profile.newLanguage" />
        <input type="button"
          value="Add"
          add-language
          needs-disabled="reachThreshold()"
          ng-disabled="needsDisabled"
          languages="profile.languages"
          new-language="profile.newLanguage" />
        <div>
            <ul>
                <li ng-repeat="language in profile.languages"></li>
            </ul>
        </div>
    </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
angular.module('DemoApp', [])
    .controller('DemoController', ["$scope",
        function ($scope) {
            $scope.profile = {};
            $scope.profile.languages = ['a', 'a', 'a', 'a', 'a'];
            $scope.reachThreshold = function () {
                return $scope.profile.languages.length === 5;
            };
        }
    ])
    .directive('addLanguage', [
        function () {
            return {
                scope: {
                    languages: '=',
                    newLanguage: '=',
                    needsDisabled: '='
                },
                link: function (scope, ele, attr) {
                    ele.on('click', function () {
                        scope.languages.push(scope.newLanguage);
                        scope.$apply();
                    });
                }
            };
        }
    ]);

Now ngDisabled works, demo

As you can see from the code, we declare a new attribute needs-disabled which use reachThreshold() as it’s value, then we set needsDisabled to ng-disabled, the last thing is to declare the new attribute in directive’s scope, in this way, ngDisabled back again.

NO Multiple isolated scope

If you put more than one directive on a element, and each of them has a isolated scope, angular will fail and complain multiple isolated scope on one element.

Master AngularJS - Directives

| Comments

I’ve been using angularjs for several months, at the beginning, i always think everything in jQuery way, since I’ve used jQuery heavily on all of my previous projects, but day by day I became like angularjs, i realize the shortcoming of jQuery: you will not know who is responsile for the event happens on this element until you see the event binding in jQuery code; lots of manipulation is based on css selector which is fragile; it’s also hard and tricky to transfer data between different element …

So I decide to write a series of articles to record what I’ve learned in the past months, these articles are not for beginners, I assume you have a basic concept with AngularJS, but still don’t know how to write AngularJS in the right way. Let’s start with directives .

Directive Definition Object

A directive usually appears as a element/tag name or attribute, it’s used to add additional functionality to the element, like, <a toggle-background/>, the directive toggleBackground will switch the background color of the current page once you click it, it’s more clear than implement the same logic with jQuery, you will understand what will happen when you see the directive on the link.

Let’s take a closer look to directives, below is a simple directive defination.

1
2
3
4
5
6
7
8
angular.module("app", []).directive("alert", [
    function () {
        return {
            restrict: 'EA',
            link: function (scope, elm, attr) {}
        };
    }
]);

Link Function

we can ignore the restrict property, by default a directive can appear as a attribute, let’s goes into the most important part in a directive: the link function .

The link function take three parameters: scope, elm, attr, the last two are easy to understand:

  • elm the jQuery object representation of the element which this directve blongs to

Take below code as an example, then most common use of directive is to do something against the element it decorated, in below example, we add autocomplete functionality for a input field.

1
2
3
4
5
6
<body ng-app="DemoApp">
    <div>
        What's your favorite programming language:
        <input type="search" search-language/>
    </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
angular.module('DemoApp', [])
    .directive('searchLanguage', [
        function () {
            return {
                link: function (scope, ele, attr) {
                    jQuery(ele).autocomplete({
                        source: ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby"]
                    });
                }
            };
        }
    ]);

See demo here

  • attr all the attributes on this element, it’s a map of attribute name and value, given <a type="text" some-directive/>, attr.type will return text attr are used to pass additional information to directive, it has the same functionallity with jQuery(element).attr('attrName'), but more convenient.

In below exmaple, we config the autocomplete dropdown is triggered at least user input 3 characters.

1
2
3
4
5
6
<body ng-app="DemoApp">
    <div>
        What's your favorite programming language:
        <input type="search" search-language min-length="3"/>
    </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
angular.module('DemoApp', [])
    .directive('searchLanguage', [
        function () {
            return {
                link: function (scope, ele, attr) {
                    jQuery(ele).autocomplete({
                        source: ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby"],
                        minLength: attr.minLength
                    });
                }
            };
        }
    ]);

see demo here

(Isolated) Scope

Why we need scope for directive ? First, let’s devide all kinds of directves into two groups:

  • without scope If what we do in this the directive is only DOM manipulation, then elm and attr is enough for use, we don’t need to declare a isolated scope.

  • with scope If we need to manipulate angular model in directive, then we need to declare a scope.

directive which don’t need to use scope is easy to understand, usually we bind event listener on the link function, but directive which need scope requires more practice to master it, we also can devide this kind of directives into two groups: manipulate model in controller and call method in controller

Manipulate Model in Controller

access model in controller need to establish a isolated scope, there are two ways:

  • @[attributeName] return the value of that attributeName, the value is a plain string, it’s the same as using attr.attributeName
  • =[attributeName] two way binding, first get the value of that attributeName, then evaluate the value in controller scope, changing the value in directive will reflect in controller scope
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body ng-app="DemoApp">
    <div ng-controller="DemoController">
        Programming language (up to five):
        <input type="search" ng-model="profile.newLanguage">
        <input type="button"
          value="Add"
          add-language
          languages="profile.languages"
          new-language="profile.newLanguage" />
        <div>
            <ul>
                <li ng-repeat="language in profile.languages">
                </li>
            </ul>
        </div>
    </div>

</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
angular.module('DemoApp', [])
    .controller('DemoController', ["$scope",
        function ($scope) {
            $scope.profile = {};
            $scope.profile.languages = [];
        }
    ])
    .directive('addLanguage', [
        function () {
            return {
                scope: {
                    languages: '=',
                    newLanguage: '='
                },
                link: function (scope, ele, attr) {
                    ele.on('click', function () {
                        scope.languages.push(scope.newLanguage);
                        scope.$apply();
                    });
                }
            };
        }
    ]);

check the demo here.

In the above code, we establish a isolateds scope to setup a bridge with languages and newLanguage in controller’s scope, then we can manipulate them.

Call Method in Controller

  • &[attributeName] return the value of that attributeName, the value is a function reference which points to the a method whose name same as the value in controller.

we are two type of invocation of controller method: without parameters and with parameters.

without parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body ng-app="DemoApp">
    <div ng-controller="DemoController">
        Programming language (up to five):
        <input type="search" ng-model="profile.newLanguage">
        <input type="button"
          value="Add"
          add-language
          languages="profile.languages"
          send-signal-to-server="sendSignalToServer()"
          new-language="profile.newLanguage" />
        <div>
            <ul>
                <li ng-repeat="language in profile.languages">
                </li>
            </ul>
        </div>
    </div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
angular.module('DemoApp', [])
    .controller('DemoController', ["$scope",
        function ($scope) {
            $scope.profile = {};
            $scope.profile.languages = [];
            $scope.clearValue = function () {
                $scope.newLanguage = "";
            };
            $scope.sendSignalToServer = function () {
                console.log('sending signal to server');
            };
        }
    ])
    .directive('addLanguage', [
        function () {
            return {
                scope: {
                    languages: '=',
                    newLanguage: '=',
                    sendSignalToServer: '&'
                },
                link: function (scope, ele, attr) {
                    ele.on('click', function () {
                        scope.languages.push(scope.newLanguage);
                        scope.sendSignalToServer();
                        scope.$apply();
                    });
                }
            };
        }
    ]);

see demo here.

we want to call a methhod in controller which send some signal to server from our directive, we declare an attribute send-signal-to-server whose value is the method name is controller, as a result, scope.sendSignalToServer hold a reference to method sendSignalToServer() .

with parameters

Continue with the above example, we change the controller method sendSignalToServer() to accept two parameters, pass parameters from directive to controller is a lillte tricky.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body ng-app="DemoApp">
    <div ng-controller="DemoController">
        Programming language (up to five):
        <input type="search" ng-model="profile.newLanguage">
        <input type="button"
          value="Add"
          add-language
          languages="profile.languages"
          send-signal-to-server="sendSignalToServer(param1, param2)"
          new-language="profile.newLanguage" />
        <div>
            <ul>
                <li ng-repeat="language in profile.languages">
                </li>
            </ul>
        </div>
    </div>

</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
angular.module('DemoApp', [])
    .controller('DemoController', ["$scope",
        function ($scope) {
            $scope.profile = {};
            $scope.profile.languages = [];
            $scope.clearValue = function () {
                $scope.newLanguage = "";
            };
            $scope.sendSignalToServer = function (param1, param2) {
                console.log('sending signal to server', param1, param2);
            };
        }
    ])
    .directive('addLanguage', [
        function () {
            return {
                scope: {
                    languages: '=',
                    newLanguage: '=',
                    sendSignalToServer: '&'
                },
                link: function (scope, ele, attr) {
                    ele.on('click', function () {
                        scope.languages.push(scope.newLanguage);
                        scope.sendSignalToServer({
                            "param1": "123",
                            "param2": "456"
                        });
                        scope.$apply();
                    });
                }
            };
        }
    ]);

see demo

as you can see, we need to define placeholder for the argument list in the attribute, and in the directive, when we are going to call that method, we need to construct a object which use the place holder as keys, and your real parameters as values.

If you’re patient enough to read to here, i belieave you’ve got a basic concept how to write directive in the right way. But keep in mind, don’t use too many directives on one element, it’s difficult to understand which directive is responsible for which functionality, thus increase the effort to maintain the code, also your directive could impact the native angular directive, so first try to use angular native directives(e.g. ng-click, ng-init), if it can not fit your requirements, write you own.