Question

What I am trying to achieve: trigger an angular js directive on a jquery ui event which in turn calls a method in the controller, which adds a value in an array which should appear in the view because of the two way data binding.

What does not work: the value is properly added in the array but the view is not updated

HTML

<li id="collection_{{$index+1}}" class="collection" ng-repeat="collection in column | filter:avoidUndefined" ng-controller="collectionController" ng-model="collection">
    <h1 class="collection-title"> {{ collection.title }}</h1>
    <ul class="bookmarks sortable droppable" droppable=".draggable" sortable=".bookmarks.sortable">
        <li class="bookmark" ng-repeat="bookmark in collection.bookmarks">
            <a class="bookmark-link bookmark-{{bookmark.id}}" href="{{bookmark.url}}">{{bookmark.title}}</a>
        </li>
    </ul>
</li>

DIRECTIVE

directives.directive('droppable',function(){
  return {
    link:function(scope,el,attrs){
        el.droppable({
            accept: attrs.droppable,
            drop: function( event, ui ) {            
                var url = $(ui.helper).text();
                scope.addBookmark(null, url, url);
                $(this).removeClass('ui-state-highlight');
            },
...

CONTROLLER

$scope.addBookmark = function (id, title, url){
    if (id == null) {
        ...
    }
    var bookmark = {
        'id': id,
        'title': title,
        'url': url,
        'type': 'bookmark'
    };
    $scope.collection.bookmarks.push(bookmark);
};

$scope.collection.bookmarks is updated properly but the view stays the same. If I call the same addBookmark function directly with a normal button it works.

Was it helpful?

Solution

You forgot to wrap your drop-callback in $apply:

directives.directive('droppable',function(){
  return {
    link:function(scope,el,attrs){
        el.droppable({
            accept: attrs.droppable,
            drop: function( event, ui ) {            
                scope.$apply(function(scope){
                    var url = $(ui.helper).text();
                    scope.addBookmark(null, url, url);
                    $(this).removeClass('ui-state-highlight');
                });
            },
...

OTHER TIPS

My recommendation is to use $emit instead of calling a method of the controller directly in your directive.

Directives should be always independent components, if inside the directive there is a call to a method from a controller(outside the directive) this will create a dependency between my directive and the controller and of course this will force one not being able to exist without the other.

If I would have to apply a design principle to a directive it will be the S in SOLID, Single responsibility principle. Directives should be able to encapsulate and work independently.

I would probably try this on my directive: scope.$emit("UrlChanged", url);

Something like this:

directives.directive('droppable',function(){
  return {
    link:function(scope,el,attrs){
        el.droppable({
            accept: attrs.droppable,
            drop: function( event, ui ) {            
                    var url = $(ui.helper).text();
                    scope.$emit("UrlChanged", url);
                    $(this).removeClass('ui-state-highlight');

            },

On my controller:

$scope.$on("UrlChanged", function(event, url){
   ... //your has changed.    
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top