101 votes

AngularJS : la liste ng-repeat n'est pas mise à jour lorsqu'un élément de modèle est épissé du tableau de modèles

J'ai deux contrôleurs et je partage les données entre eux avec une fonction app.factory.

Le premier contrôleur ajoute un widget dans le tableau du modèle (pluginsDisplayed) lorsqu'un lien est cliqué. Le widget est poussé dans le tableau et ce changement est reflété dans la vue (qui utilise ng-repeat pour afficher le contenu du tableau) :

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

Le widget est construit sur trois directives, k2plugin, remove et resize. La directive remove ajoute un span au modèle de la directive k2plugin. Lorsque l'on clique sur ce span, l'élément de droite dans le tableau partagé est supprimé avec la commande Array.splice() . Le tableau partagé est correctement mis à jour, mais le changement est pas reflétée dans la vue. Cependant, lorsqu'un autre élément est ajouté, après la suppression, la vue est rafraîchie correctement et l'élément précédemment supprimé n'apparaît pas.

Qu'est-ce que je ne comprends pas ? Pouvez-vous m'expliquer pourquoi cela ne fonctionne pas ? Existe-t-il un meilleur moyen de faire ce que j'essaie de faire avec AngularJS ?

Voici mon 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>

C'est mon 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 immediately. 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);
        })
    };
});

132voto

Mathew Berg Points 7247

Chaque fois que vous effectuez une opération en dehors d'AngularJS, comme un appel Ajax avec jQuery, ou que vous liez un événement à un élément comme ici, vous devez faire savoir à AngularJS qu'il doit se mettre à jour. Voici le changement de code que vous devez faire :

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();
        })
    };
});

Voici la documentation qui s'y rapporte : https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

53voto

avk Points 566

Si vous ajoutez un $scope.$apply(); juste après $scope.pluginsDisplayed.splice(index,1); alors ça marche.

Je ne suis pas sûr de la raison pour laquelle cela se produit, mais en gros, quand AngularJS ne sait pas que le $scope a changé, il doit appeler $apply manuellement. Je suis également nouveau dans AngularJS et ne peux donc pas mieux expliquer ce phénomène. J'ai besoin d'y regarder de plus près.

J'ai trouvé cet article génial qui l'explique très bien. Note : Je pense qu'il serait mieux d'utiliser ng-click (docs) plutôt que de se lier à "mousedown". J'ai écrit une application simple ici ( http://avinash.me/losh , source http://github.com/hardfire/losh ) basé sur AngularJS. Il n'est pas très propre, mais il peut être utile.

7voto

shreedhar bhat Points 4223

J'ai eu le même problème. Le problème était dû au fait que "ng-controller" était défini deux fois (dans le routage et aussi dans le HTML).

1voto

Bassem Zaitoun Points 21

Enlevez "track by index" du ng-repeat et le DOM sera rafraîchi.

0voto

user3856437 Points 388

Il y a un moyen facile de le faire. Très facile. Depuis que j'ai remarqué que

$scope.yourModel = [];

supprime toute la liste du tableau $scope.yourModel vous pouvez faire comme ceci

function deleteAnObjectByKey(objects, key) {
    var clonedObjects = Object.assign({}, objects);

     for (var x in clonedObjects)
        if (clonedObjects.hasOwnProperty(x))
             if (clonedObjects[x].id == key)
                 delete clonedObjects[x];

    $scope.yourModel = clonedObjects;
}

Le $scope.yourModel sera mis à jour avec les clonedObjects.

J'espère que cela vous aidera.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X