Angular JS directives with isolate scopes and internal controllers

425 Views Asked by At

I haven't touched angular js in a while and back when I did write, we used a flavour with typescript which was pretty straightforward with me. Now I want to write vanilla angular js and I feel I am a bit confused.

Problem:
I have a directive with a few variables in its isolate scope and I basically want to bind to this directive that is spawned inside a for each <ul> to a click event. I tried with directly binding a function on ng-click and with link element e.t.c. bind on click, but it seems I am doing something wrong since with the first way nothing happens, with the second way the two-way bound variable is undefined.

Here it goes: https://plnkr.co/edit/OOBMs8pYONLjUE9lQXla?p=preview

activity-header.html

<div>
<h4>
Activity Name: {{activity.activity_name}}
</h4>

<h6>    
Activity Start Date: {{activity.activity_start_date}}
</h6>

<h6>        
Activity End Date: {{activity.activity_end_date}}
</h6>

<h6>
Participants: {{activity.participants}}
</h6>
</div>

activity-header.js

var app = angular.module('mainApp');

/*
app.controller('activityHeaderCtrl', ['$scope', function($scope) {
    $scope.activity='';
    $scope.msg='';
    $scope.check = function() {
            alert($scope.msg);
        };
}]);
*/

app.directive('activityHeader', function() {
    return {
        restrict: 'AE',
        templateUrl: 'activity-header.html',
        controller: ['$scope', Controller],
        scope: {
            activity:'=',
            msg:'='         
        },
        link: function($scope, $element, attrs) {
        $element.bind('click', function($scope) {
            alert($scope.msg);
        })}
    };        
    function Controller($scope) {
        $scope.check = function() {
            alert($scope.msg);
        };

    }
});

index.html

<html ng-app="mainApp">

    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
    <script src="script-main.js"></script>
    <script src="activity-header.js"></script>

<body>

    <div ng-controller="ctrl">

        <h1>
            Major Bla bla System    
        </h1>

        <ul>        
            <li ng-repeat="x in events">                
                <div activity-header activity="x" msg="greetingsfriend" ng-click="check()"></div>
            </li>
        </ul>    

        <h6>
            Beta v.0.2
        </h6>

    </div>

</body>

</html>

script-main.js

var app = angular.module('mainApp', []);

app.controller('ctrl', function ($scope) {

    //$scope.events = ["Elections", "Protest", "Martial Law", "X-mas Celebration"];
    $scope.events = [
        {"activity_name": "Elections", "activity_start_date": "31/12/2014", "activity_end_date": "31/12/2015", "participants": "1453"},
        {"activity_name": "Martial Law", "activity_start_date": "31/12/2014", "activity_end_date": "31/12/2015", "participants": "1821"},
        {"activity_name": "Protest", "activity_start_date": "31/12/2014", "activity_end_date": "31/12/2015", "participants": "1940"},
        {"activity_name": "X-mas Celebration", "activity_start_date": "31/12/2014", "activity_end_date": "31/12/2015", "participants": "2009"}
    ];

    $scope.salute = function () {
        alert('hello there');
    };

});

(By the way, I'm using Mozilla Firefox, otherwise I'd have to host it e.g. on node.js for the same origin policy, don't know how to turn it off in chrome/ internet explorer).

3

There are 3 best solutions below

3
On BEST ANSWER

Any idea on how to handle the click?

Your problem is that you are using $scope as the name for the first argument in the click handler function. That name is overriding the $scope argument from the linking function.

    //BAD
    link: function($scope, $element, attrs) {
    $element.bind('click', function($scope) {
        alert($scope.msg);
    })}

Fix your code like this:

    //GOOD
    link: function(scope, element, attrs) {
        element.on('click', function clickHandler(event) {
            console.log(scope.msg);
        });
    }

AngularJS jqLite invokes the click handler with a JQuery Event Object as the first argument not $scope.


Is this the correct way to handle events on directives?
Why doesn't the ng-click event ever work to call the function I have in my directive's controller?

The ng-click directive binds to functions in the parent scope.

To bind click events to functions on the directive's scope, use element.on(). It's how ng-click gets the event from the browser. Look at the source code.

2
On

depends on what you want to obtain.

For example, if you want to bind a function of your directive on click you don't need the link function. You can simply bind a click on your outer div with ng-click. See this example: http://jsbin.com/sanova/edit?html,js,output

But if you want to call a function on your parent controller you need to pass a reference to that function with a property on your directive. See this example: http://jsbin.com/peqasu/edit?html,js,output

As you can see i've put in both example a ng-click directive on the outer div in your directive template. On click the check function on the directive controller is invoked. In the first example simply alert the message, in the second one calls the greetFunction passed as a property of your directive.

1
On

First of all you need to know the difference between smart and dumb components (directives).

Here you can read a very good article explain the difference from smart and dumb components written by Dan Abramov, the creator of Redux: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

When you understand this difference you can try to write more dumb components and less smart one. This means that you need to keep all the logic on your parents components and pass it down to your dumb components.

In previous examples we do that only in the second one. In fact, in that example we keep the logic (our check function) in our parent component and only pass a reference to it. In this way the activity-header component have no idea of what to do when a click is done. It only know that it must call a function, what this function does is not its problem.

This is a good approach to have in a complex application, so you can reuse your components in different ways simply changing the reference function.