ng-repeat list in Angular is not updated when a model element is spliced from the model array

Hi everybody, I have two controllers and share data between them with an app.factory function. The first controller adds a widget in the model array (pluginsDisplayed) when a link is clicked. The widget is pushed into the array and this change is reflected into the view (that uses ng-repeat to show the array content):

<div ng-repeat="pluginD in pluginsDisplayed">
    <div k2plugin pluginname="{{pluginD.name}}" pluginid="{{pluginD.id}}"></div>
</div>

The widget is built upon three directives, k2plugin, remove and resize. Remove directive adds a span to the template of the k2plugin directive. When said span is clicked, the right element into the shared array is deleted with Array.splice(). The shared array is correctly updated, but the change is not reflected in the view. However, when another element is added, after the remove, the view is refreshed correctly and the previously-deleted element is not shown.

What I'm getting wrong? Could you explain me why this doesn't work? Is there a better way to do what I'm trying to do with Angular?

This is my index.html:

<!doctype html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js">
    </script>
    <script src="main.js"></script>
  </head>
  <body>
    <div ng-app="livePlugins">
        <div ng-controller="pluginlistctrl">
            <span>Add one of {{pluginList.length}} plugins</span>
            <li ng-repeat="plugin in pluginList">
                <span><a href="" ng-click="add()">{{plugin.name}}</a></span>
            </li>
        </div>
        <div ng-controller="k2ctrl">
            <div ng-repeat="pluginD in pluginsDisplayed">
                <div k2plugin pluginname="{{pluginD.name}}" pluginid="{{pluginD.id}}"></div>
            </div>
        </div>
    </div>
  </body>
</html>

This is my main.js:

var app = angular.module ("livePlugins",[]);

app.factory('Data', function () {
    return {pluginsDisplayed: []};
});

app.controller ("pluginlistctrl", function ($scope, Data) {
    $scope.pluginList = [{name: "plugin1"}, {name:"plugin2"}, {name:"plugin3"}];
    $scope.add = function () {
        console.log ("Called add on", this.plugin.name, this.pluginList);
        var newPlugin = {};
        newPlugin.id = this.plugin.name + '_'  + (new Date()).getTime();
        newPlugin.name = this.plugin.name;
        Data.pluginsDisplayed.push (newPlugin);
    }
})

app.controller ("k2ctrl", function ($scope, Data) {
    $scope.pluginsDisplayed = Data.pluginsDisplayed;

    $scope.remove = function (element) {
        console.log ("Called remove on ", this.pluginid, element);

        var len = $scope.pluginsDisplayed.length;
        var index = -1;

        // find the element in the array
        for (var i = 0; i < len; i += 1) {
            if ($scope.pluginsDisplayed[i].id === this.pluginid) {
                index = i;
                break;
            }
        }

        // remove the element
        if (index !== -1) {
            console.log ("removing the element from the array, index: ", index);
            $scope.pluginsDisplayed.splice(index,1);
        }

    }
    $scope.resize = function () {
        console.log ("Called resize on ", this.pluginid);
    }
})

app.directive("k2plugin", function () {
    return {
        restrict: "A",
        scope: true,
        link: function (scope, elements, attrs) {
            console.log ("creating plugin");

            // this won't work immediatley. attribute pluginname will be undefined
            // as soon as this is called.
            scope.pluginname = "Loading...";
            scope.pluginid = attrs.pluginid;

            // observe changes to interpolated attribute
            attrs.$observe('pluginname', function(value) {
                console.log('pluginname has changed value to ' + value);
                scope.pluginname = attrs.pluginname;
            });

            // observe changes to interpolated attribute
            attrs.$observe('pluginid', function(value) {
                console.log('pluginid has changed value to ' + value);
                scope.pluginid = attrs.pluginid;
            });
        },
        template: "<div>{{pluginname}} <span resize>_</span> <span remove>X</span>" +
                       "<div>Plugin DIV</div>" + 
                  "</div>",
        replace: true
    };
});

app.directive("remove", function () {
    return function (scope, element, attrs) {
        element.bind ("mousedown", function () {
            scope.remove(element);
        })
    };

});

app.directive("resize", function () {
    return function (scope, element, attrs) {
        element.bind ("mousedown", function () {
            scope.resize(element);
        })
    };

});

Whenever you do some form of operation outside of angularjs, such as doing an ajax call with jquery, or binding an event to an element like you have here you need to let angular know to update itself. Here is the code change you need to do:

app.directive("remove", function () {
    return function (scope, element, attrs) {
        element.bind ("mousedown", function () {
            scope.remove(element);
            scope.$apply();
        })
    };

});

app.directive("resize", function () {
    return function (scope, element, attrs) {
        element.bind ("mousedown", function () {
            scope.resize(element);
            scope.$apply();
        })
    };

});

Here are the docs on it: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

If you add a $scope.$apply(); right after $scope.pluginsDisplayed.splice(index,1); then it works.

I am not sure why this is happening, but basically when angular doesn't know that the $scope has changed, it requires to call $apply manually. I am also new to angularjs so cannot explain this better. Need too look more into it. Hope this helps.

Edit : Found this awesome article that explains it quite properly. Note : I think it might be better yo use ng-click (docs) rathar than binding to "mousedown" . I wrote a simple app here (http://avinash.me/losh , source http://github.com/hardfire/losh) based on angular js. Not very clean but might be of help.