0

I need to remove item and push next inside ng-repeat if image is not exist.

Currently i using next directive

myApp.directive("noImage", function() {
  return {
    link: function(scope, element, attrs) {
      return element.bind("error", function() {
        element.attr("src", attrs.noImage);
        return element.addClass('no-img');
        //return element.parent().remove();
        //return element.parent().splice();
      });
    }
  };
});

Obviously if use element.parent().remove() or splice() it does not push next item to array.

Here is fiddle

As another idea is write function in controller and then run it from directive:

$scope.splicePost = (post) =>
  $scope.posts.splice( $scope.posts.indexOf(post), 1 )

The problem that i can't get how to do that. Maybe need to use isolate scope?

3 Answers 3

2

ng-repeat creates a child scope for each item in the repeater.

This means that inside the directive you will inherit the parent scope array as well as have access to scope.post for each post item.

myApp.directive("noImage", function () {
    return {
        link: function (scope, element, attrs) {
            element.bind("error", function () {
                // get index of post in the posts array
                var idx = scope.posts.indexOf(scope.post);
                scope.$apply(function () {
                    // splice posts array
                    scope.posts.splice(idx, 1);
                });
            });
        }
    };
});

Since the event is outside of angular core you need to tell angular to run a digest when the scope is changed and this is done using $apply or $timeout

To make this more re-useable it would be better to create isolated scope and pass in the post item and post array to the isolated scope

DEMO

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for this. Can you please take a look on this question (it related to this one) stackoverflow.com/questions/33472524/…
0

Yes you are correct, you will need to pass the array into the directive, and if the image cannot load, you can splice from the array. pass it in isolated scope with '=', so you have two way binding.

If an error happens, splice, otherwise, do nothing.

UPDATE: Code:

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

myApp.directive("noImage", function() {
  return {
    scope: {
      posts: "="
    },
    link: function(scope, element, attrs) {
      return element.bind("error", function() {
        scope.posts.splice(scope.$parent.$index, 1);
        scope.$apply();
      });
    }
  };
});

The html will become:

<div ng-controller="MyCtrl">
  <ul>
     <li ng-repeat = 'post in posts | limitTo: 5'>
         <img posts="posts" ng-src = "{{post.image_url}}" no-image = "" />

     </li>
  </ul> 
</div>

Comments

0

I would rather think in components, means you could create a component called image-block which has the template you used in your markup before. Your markup now could look like this:

<div ng-controller="MyCtrl as vm">
  <image-block images="vm.posts" url-property="image_url"></image-block>
</div>

You pass in the images and the urlProperty under which the component can find the url for each image. The image-block directive is implemented as follows:

myApp.directive("imageBlock", function() {
  return {
    restrict: 'E',
    scope: {
      images: '=',
      urlProperty: '@',
      limit: '@'
    },
    template:
      '<ul>' +
        '<li ng-repeat="image in images | limitTo: limit || 3">' + 
          '<img ng-src="{{ image[urlProperty] }}" is-image="image" />' +
        '</li>' +
      '</ul>',
    controller: ['$scope', function(scope) {
        this.removeImage = function(image) {
            var index = scope.images.indexOf(image);
            if(index > 0) {
                scope.images.splice(index, 1);
            }
        };
    }]
  };
});

The component has its own controller for logic, which also gets required by the isImage directive. This directive will catch the error event and calls the removeImage function of the parent controller.

The isImage directive looks like this:

myApp.directive("isImage", function() {
  return {
    scope: {
        image: '=isImage'    
    },
    require: '^imageBlock',
    link: function(scope, element, attrs, ctrl) {
      return element.bind("error", function() {
         scope.$apply(ctrl.removeImage(scope.image));
      });
    }
  };
});

The only scope property is the image, which will be passed along to the parent controller to remove the image from the list.

Here is an updated version of your JSFiddle. Personally I find the way of thinking in components very useful and it helps to break down your logic and UI into smaller pieces.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.