3

I have this two directives, one nested inside each other :

<envato class="container content-view-container" data-ng-cloak data-ng-hide="spinner">
    <items data-ng-repeat="items in marketplaces"></items>
</envato>

And each of those two are defined as such :

Application.Envato.directive("envato", ["$timeout", function($timeout){

    var object = {

        restrict : "E",
        controller : "EnvatoAPIController",
        transclude : true,
        replace : true,
        templateUrl : "templates/envato-view.php",
        link : function(scope, element, attrs, controller) {

            console.log(scope);

            return controller.getLatestItems().then(function(data) {



                scope.marketplaces = angular.fromJson(data);
                scope.count = scope.marketplaces.length;

                var tst = angular.element(element).find(".thumbnails");

                /* $timeout(function() { scope.swiper = new Swipe(document.getElementById('swiper-container')); }, 5000); */                    
                scope.spinner = false;
            });

        }
    };

    return object;
}]);

Application.Envato.directive("items", function(){

    var iterator = [],
        object = {

            require : "^envato",
            restrict : "E",
            transclude : false,
            replace : true,
            templateUrl : "templates/envato-items-view.php",
            link : function(scope, element, attrs, controller) {

                iterator.push(element);

                if (iterator.length === scope.$parent.$parent.count) { console.log(iterator); };                                    
            }
        };

    return object;
});

A lot of the code above might not make a lot of sense because it's part of a bigger application, but I hope it does for my question. What I'm trying to do is to change a scope property of the directive envato from the directive items. Because I have a iteration and I want to know when it's done so I can do another operation on the appended DOM elements during that iteration.

For instance let's say I will have the scope.swipe defined inside the directive envato, and watch it for changes. In the directive items, I will watch when the ng-repeat is done and then change the above defined scope property scope.swipe. This will trigger the change inside the directive envato, and now I will know that I can do my operation.

I hope that I'm clear enough, if not I could try having more code or I'll try being more specific. How could I achieve what I just described above ?

EDIT : I do know that using : console.log(angular.element(element.parent()).scope()); inside the directive items will give me the scope of the envato directive, but I was wondering if there was a better way of doing it.

Roland
  • 9,321
  • 17
  • 79
  • 135

5 Answers5

4

For this kind of inter-directive communication, I recommend defining an API/method on your envato directive that your items directive can call.

var EnvatoAPIController = function($scope) {
    ...
    this.doSomething = function() { ... }
}

Your items directive already requires the envato directive, so in the link function of your items directive, just call the the API when appropriate:

require : "^envato",
link : function(scope, element, attrs, EnvatoCtrl) {
   ...
   if(scope.$last) {
       EnvatoCtrl.doSomething();
   }
}

What is nice about this approach is that it will work even if you someday decide to use isolate scopes in your directives.

The tabs and pane directives on the AngularJS home page use this communication mechanism. See https://stackoverflow.com/a/14168699/215945 for more information. See also John's Directive to Directive Communication video.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
1

Use scope.$eval('count') at item directive and let angular resolve for you.

Caio Cunha
  • 23,326
  • 6
  • 78
  • 74
  • And can I apply new values to it ? – Roland Mar 03 '13 at 16:59
  • Yes, you can evaluate any [valid angular expression](http://docs.angularjs.org/guide/expression). You can even pass local values: `scope.$eval('scopeValue + localValue', {localValue: 15})`. Note that on assigment, the child scope will have it value changed, not the parent. In this case, you should use a function at parent to allow you to change the value (i.e. `scope.$eval('changeValue(12345)')`). Take a look [here](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance) for more info on scope inheritance. Read the ng-repeat docs. – Caio Cunha Mar 03 '13 at 17:25
  • I see, so if I watch for thee `count` property in the parent directive `envato` : `scope.$watch("count", function(old, current, s) { console.log(old, current, s); });` ; the watch will see the change right ? Actually, the `count` was just an example, I would only be setting the property from false to true in order to continue with another operation ( or any value that tells me that I can continue with applying the function that needs to be applied after having the template appended to the DOM / rendered in ). In this case what would be the right expression for changing that with `$eval` ? – Roland Mar 03 '13 at 17:31
  • I just have tried `scope.$eval('swipe = true');`, but this one just adds a property `swipe` to my `items` scope instead of the parent scope ( the `envato` scope ). – Roland Mar 03 '13 at 17:45
  • How about using `$apply()`, I've read that it's preferred over `$eval`. I did get one good thing out of your comment, using `scope.$last` will give me a true if I'm on the last iteration ( which is very useful since I just used another way of finding that out ). But I couldn't get the `use a function at parent to allow you to change the value (i.e. scope.$eval('changeValue(12345)')).` part, what exactly is it that I have to do there ? – Roland Mar 03 '13 at 18:13
  • As you saw, no `$watch` is run, as the change is only in the child. About the `$apply`, it internally uses an `$eval` and issue a `$digest cycle`. You can't use it if already in a `$digest` (a `ng-click` or `$watch`, for example) At parent scope you create a function `parentScope.iterate = function() { parentScope.count++; }`. At the child, you call `$eval('iterate()');`. This way, you'll iterate the parent's property. – Caio Cunha Mar 04 '13 at 11:14
1

I think you are looking for a callback that gets called when the ng-repeat completes. If that's what you want, i have created a fiddle. http://jsfiddle.net/wjFZR/.

There is no much of UI in the fiddle. Please open the firebug console, and run the fiddle again. You will see an log. That log is called at the end of an ng-repeat defined in the cell directive.

$scope.rowDone = function(){ console.log($scope) } this is the callback function that is defined on the row directive that will get called when the ng-repeat of the cell directive is completed.

It is registered in this way.

<cell ng-repeat="data in rowData" repeat-done="rowDone()"></cell>

Disclaimer: I'm too a newbie in angularjs.

Rajkamal Subramanian
  • 6,884
  • 4
  • 52
  • 69
  • I actually found a way to do it with @CaioToOn's suggestions and a little but of research. I can do it from the `items` directive, I just need to check when `scope.$last` has been switched to true ( which means that I've just looped over the last item in the object ). But you're example was the first thing I though, but I wanted to keep this away from the controller and keep it in the directive it's self :) – Roland Mar 04 '13 at 07:42
1

Hmmm it appears you are trying to make it difficult for yourself. In your directive you do not set a scope property:

var object = {
    restrict : "E",
    transclude : true,
    replace : true,
    scope: true,
    ...

Setting scope: {} will give your directive an fully isolated new scope.

BUT setting scope: true will give your directive a fully isolated new scope that inherits the parent.

I use this method to contain the model in the top level parent directive and allow it to filter down through all the child directives.

sidonaldson
  • 24,431
  • 10
  • 56
  • 61
0

I love Mark's answer but I eventually created an attribute directive to save element directives' scopes to the rootScope like so:

myApp.directive('gScope', function(){
    return {
        restrict: 'A',
        replace: false,
        transclude: false,
        controller: "DirectiveCntl",
        link: function(scope, element, attrs, controller) {
            controller.saveScope(attrs.gScope);
        }
    }
});

...

function DirectiveCntl($scope, $rootScope) {
    this.saveScope = function(id) {
        if($rootScope.directiveScope == undefined) {
            $rootScope.directiveScope = [];
        }
        $rootScope.directiveScope[id] = $scope;
    };
}

...

<span>Now I can access the message here: {{directiveScope['myScopeId'].message}}</span>
<other-directive>
    <other-directive g-scope="myScopeId" ng-model="message"></other-directive>
</other-directive>

Note: While this makes it a snap to collect data from all your various directives it comes with my word of caution that now you have to ensure the potential pile of scopes are properly managed to avoid causing a memory leak on pages. Especially if you are using the ng-view to create a one page app.

Erich
  • 2,743
  • 1
  • 24
  • 28