1

I've read some similar answers to my question, such as this one though can't translate it to my own requirements - comes from a lack of understanding...

I have a controller:

appControllers.controller('TransactionsCtrl', ['$scope', 'InfiniteScrollingDataService', function ($scope, dataService) {
    // setup our scope properties
    $scope.$root.title = 'Transactions';
    var urlQuery = {
        skip: 0,
        take: 25,
        search: '',
        order: 'DateTimeCreated',
        orderBy: 'desc',
        transactionTypeID: null,
        transactionStatusID: null
    };
    var apiUrl = 'api/transactions';
    $scope.transactions = new dataService(apiUrl, urlQuery);

    $scope.Filter = function (senderParent, type, id) {
        $scope.FilterApplied = true;
        console.log('filter in controller: ' + senderParent + ' ' + type + ' ' + id);
    }
}]);

And I have a directive:

appDirectives.directive('bootstrapListItems', ['$rootScope', function ($rootScope) {

    return {
        restrict: 'A',
        templateUrl: 'bootstrapDropDownItems.html',
        link: function (scope, element, attrs) {

            scope.type = attrs.useGlobaljsonObject;
            scope.parentElement = attrs.id;
            scope.items = [];

            var obj = $rootScope.globalJSON[scope.type];

            for (o in obj) {
                scope.items.push({ key: o, value: obj[o] })
            }
        }       
    }

}]);

And the template for my directive:

<script type="text/ng-template" id="bootstrapDropDownItems.html">

    <li class="active"><a href="#" class="lnkFilterList">- Any -</a></li>
    <li ng-repeat="item in items">
        <a href="#" class="lnkFilterList" ng-click="Filter(parentElement, type, item.key)">{{item.value}}</a>
    </li>

</script>

If I don't isolate the scope of the directive then the controller is called correctly, and i see the console logging out my arguments.

However, I (think) I need to isolate the scope as there will be multiples of this directive on the page.

when I add scope: {} to my directive the controller function is no longer called.

I also tried changing my ng-click to ng-click="$parent.Filter(.....)" - that didnt' seem to work either.

Can anybody please point me in the right direction?

Community
  • 1
  • 1
Darren Wainwright
  • 30,247
  • 21
  • 76
  • 127
  • This will help to solve your problem http://stackoverflow.com/questions/15991137/calling-method-of-parent-controller-from-a-directive-in-angularjs/15991525#15991525 – Ajay Beniwal Feb 24 '14 at 17:47

2 Answers2

3

ng-click="$parent.Filter(.....)" isn't working because you have it in an ng-repeat which also creates a (non-isolated) scope. In that case you would have to write

ng-click="$parent.$parent.Filter(.....)"

but don't do that...

You could emit an event in the click event handler and listen for it in your controller.

<script type="text/ng-template" id="bootstrapDropDownItems.html">

    <li class="active"><a href="#" class="lnkFilterList">- Any -</a></li>
    <li ng-repeat="item in items">
        <a href="#" class="lnkFilterList" ng-click="onClick(parentElement, type, item.key)">{{item.value}}</a>
    </li>

</script>

directive:

appDirectives.directive('bootstrapListItems', ['$rootScope', function ($rootScope) {

    return {
        restrict: 'A',
        templateUrl: 'bootstrapDropDownItems.html',
        link: function (scope, element, attrs) {

            scope.type = attrs.useGlobaljsonObject;
            scope.parentElement = attrs.id;
            scope.items = [];

            var obj = $rootScope.globalJSON[scope.type];

            for (o in obj) {
                scope.items.push({ key: o, value: obj[o] })
            }
            scope.onClick = function(){
                // pass an array of original arguments to the event
                scope.$emit('someEventName', Array.prototype.slice.call(arguments));
            };
        }       
    }

}]);

controller:

appControllers.controller('TransactionsCtrl', ['$scope', 'InfiniteScrollingDataService', function ($scope, dataService) {
    // setup our scope properties
    $scope.$root.title = 'Transactions';
    var urlQuery = {
        skip: 0,
        take: 25,
        search: '',
        order: 'DateTimeCreated',
        orderBy: 'desc',
        transactionTypeID: null,
        transactionStatusID: null
    };
    var apiUrl = 'api/transactions';
    $scope.transactions = new dataService(apiUrl, urlQuery);

    $scope.$on('someEventName', function(e, args){
         // call the $scope.Filter function with the passed array of arguments
         $scope.Filter.apply(null, args);
    });
    $scope.Filter = function (senderParent, type, id) {
        $scope.FilterApplied = true;
        console.log('filter in controller: ' + senderParent + ' ' + type + ' ' + id);
    }
}]);
kamilkp
  • 9,690
  • 6
  • 37
  • 56
  • Hi. I like this solution as it will allow me to, within the directive, change some class info on the selected item. However, I have pretty much copy/pasted this and for some reason my arguments are empty in my `$scope.$on('filterSelected', function(e, args){...` and all are `undefined` in my $scope.Filter function. any idea where i may be going wrong? – Darren Wainwright Feb 24 '14 at 18:35
  • Actually - if i change `Array.prototype.slice(arguments)` for just `arguments` it all works as prescribed. Not sure why (not used the slice before...) – Darren Wainwright Feb 24 '14 at 18:39
  • Any idea why Array.prototype.slice(arguments) returns an empty array? – Darren Wainwright Feb 24 '14 at 18:50
  • Because it should be called like this: `Array.prototype.slice.call(arguments)`. Sorry, my mistake, I edited the answear. – kamilkp Feb 24 '14 at 18:52
1

Having the directive used in multiple places does not mean you can't use shared scope. That determination comes down to if the directives should always show the same state (thus each inheriting and manipulating the same properties of the controller), or if they each are intended to show their own versions of state (thus being isolate). Being that you are setting a controller level variable in $scope.FilterApplied I assume you just want to repeat the same controls on the page? If so, you can stick with an inherited scope.

If not, and instead each directive needs this functionality on it's own, you are probably better off moving the Filter code into a service that the directive and controller(s) both inject so that the utility functions are shared but the settings and state of the Filter are per scope rather than shared.

Ultimately it depends on how you are using it.

Matt Pileggi
  • 7,126
  • 4
  • 16
  • 18
  • I'm assuming (due to lack of knowledge at this point) that i will need an isolate scope. There will be a few elements on the page that use this directive. Each showing different items in the list. Also when an item from a filter is selected i need to set class of the selected item. Still not sure if i need isolate scope in this case.. – Darren Wainwright Feb 24 '14 at 18:10
  • An inherit scope will simply allow it to inherit the scope of its parent rather than creating its own scope. If you are displaying the same list and it's determined by an object/property in the controller then inherit scope is your easiest path. Each instance of the directive can contain attributes to change which part of the list they display. An isolate scope would only be necessary if they manipulate and encapsulate their own copy of the data. Otherwise each time you change the controller list they all change together. It's just a prototype chain really. – Matt Pileggi Feb 24 '14 at 18:15
  • Ah, ok. it's becoming a little clearer now. In these lists there are no two-way binding or anything like that. It's simply to tell the controller to go get some data from the web api with this filter applied. I like the solution that kamilkp gave though as i can do more inside the directive on the item that was selected - change class etc. Thank you for clarification though – Darren Wainwright Feb 24 '14 at 18:36