简体   繁体   中英

Angular directive parent scope

I'm building a directive that decorates a table header item and sort the data upon the click event. I cannot apply the changes on the data model to the parent scope using the directive scope.

vm.data is an array on the parent scope that contains the data I want to sort in the directive. After the click the data object in the directive has changed but the parent is still in the same order.

I dont want to access the parent scope using $parent, What I'm missing ??

<th sortable="browser" data="vm.data">Browser</th>

directive code:

angular
    .module("app")
    .directive("sortable", ['lodash', sortableDirective]);

function sortableDirective(lodash) {
    return {
        restrict: "A",
        scope:{
            data:"="
        },
        controller:function($scope){

        },
        link: function (scope, element, attributes) {

            var sorted = undefined;
            var col = attributes['sortable'];
            var oldClass = 'sorting'

            attributes.$$element.addClass(oldClass);
            $(element).on("click", sort);

            function changeClass(){
                if(sorted=='asc'){
                    attributes.$$element.removeClass(oldClass);
                    attributes.$$element.addClass('sorting_asc');
                    oldClass = 'sorting_asc';
                }
                else if(sorted=='desc'){
                    attributes.$$element.removeClass(oldClass);
                    attributes.$$element.addClass('sorting_desc');
                    oldClass='sorting_desc';
                }

            }

            function sort() {

                if (sorted == 'asc') {
                    sorted = 'desc';
                }
                else {
                    sorted = 'asc';
                }

                scope.data = lodash.sortBy(scope.data, function (o) {
                    return o[col];
                });

                if (sorted == 'desc') {
                    lodash.reverse(scope.data);
                }

                changeClass();
            }
        }
    };
}

This is because you are using jQuery to listen to change on the element. So just change this line:

$(element).on("click", sort);

to

element.on("click", sort);

The 2nd attribute ie element is already an instance of jQlite if jQuery is not available and will be an instance of jQuery if jQuery is available.

In any case, there is a method available .on which will be executed on the value change. Since you again wrapped it to $() , the Angular was not getting notified of the change in the data.

Edit:

On the 2nd walk through of your code, I see the actual problem. You are reassigning the complete scope.data in the sort() method which is breaking the pass by reference behavior of Javascript (or in any OOPS programming).

The pass by reference will only work if you continue to modify your SAME reference variable. Noticed the word, SAME ?? By writing scope.data = lodash.sortBy(scope.data, function (o) {}) you removed the reference of the actual data passed to the directive. Hence the values are not updated.

So to fix this problem, you have a few options:

  1. Change your sorting code to not reassign the complete scope.data variable RECOMMENDED (use inbuilt sort method)
  2. Pass the modified data to the parent scope using scope.$emit()
  3. Or use the $parent property which you don't want to use

The bidirectional binding will update the parent on each digest cycle but the click handler needs to invoke that digest cycle with $apply :

    link: function (scope, element, attributes) {

        var sorted = undefined;
        var col = attributes['sortable'];
        var oldClass = 'sorting'

        element.addClass(oldClass);

        //REPLACE this
        //$(element).on("click", sort);
        //
        //WITH this           
        element.on("click", function (e) {
            sort();
            scope.$apply();
        });

        function changeClass(){
            if(sorted=='asc'){
                element.removeClass(oldClass);
                element.addClass('sorting_asc');
                oldClass = 'sorting_asc';
            }
            else if(sorted=='desc'){
                element.removeClass(oldClass);
                element.addClass('sorting_desc');
                oldClass='sorting_desc';
            }

        }

The ng-click directive automatically invokes $apply but when the click event is handled by AngularJS jqLite , the code needs to notify the AngularJS framework.

From the Docs:

$apply([exp]);

$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling , executing watches .

-- AngularJS $rootScope.scope API Reference -- $apply

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM