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

100

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

Le premier contrôleur ajoute un widget dans le tableau de modèles (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 une étendue au modèle de la directive k2plugin. Lorsque ledit span est cliqué, l'élément de droite dans le tableau partagé est supprimé avec Array.splice(). Le tableau partagé est correctement mis à jour, mais la modification n'est pas reflétée dans la vue. Cependant, lorsqu'un autre élément est ajouté, après la suppression, la vue est correctement actualisée et l'élément précédemment supprimé n'est pas affiché.

Qu'est-ce que je me trompe? Pouvez-vous m'expliquer pourquoi cela ne fonctionne pas? Existe-t-il une meilleure façon de faire ce que j'essaie de faire avec AngularJS?

Ceci est 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);
        })
    };
});
conférence de Janes
la source

Réponses:

131

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

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 à ce sujet: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

Mathew Berg
la source
4
Envisagez de déplacer le scope.remove (élément) et scope.resize (élément) dans une expression / fonction passée à $ apply.
Per Hornshøj-Schierbeck
1
@ PerHornshøj-Schierbeck Je suis d'accord, sinon Angular ne sera pas au courant des erreurs si celles-ci se produisent.
Jim Aho
2
Faites attention ! normalement, Angular appelle le cycle de résumé quand il le doit et $ apply l'appelle manuellement. C'est souvent une mauvaise pratique de l'appeler manuellement, car nous pouvons faire des erreurs d'optimisation et cela peut consommer des ressources.
Alex
Je ne comprends pas ce que vous supprimez ou redimensionnez.
Desarrollo Desafio de Guerrero
53

Si vous ajoutez un $scope.$apply();droit après, $scope.pluginsDisplayed.splice(index,1);cela fonctionne.

Je ne sais pas pourquoi cela se produit, mais fondamentalement, lorsque AngularJS ne sait pas que la portée $ a changé, il doit appeler $ apply manuellement. Je suis également nouveau sur AngularJS, je ne peux donc pas l'expliquer mieux. J'ai besoin de me pencher davantage dessus.

J'ai trouvé cet article génial qui l'explique assez correctement. Remarque: je pense qu'il serait peut-être préférable d'utiliser ng-click (docs) plutôt que de lier à "mousedown". J'ai écrit une application simple ici ( http://avinash.me/losh , source http://github.com/hardfire/losh ) basée sur AngularJS. Ce n'est pas très propre, mais cela pourrait être utile.

avk
la source
7

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

shreedhar bhat
la source
1

Supprimez "track by index" du ng-repeat et cela rafraîchirait le DOM

Bassem Zaitoun
la source
0

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

$scope.yourModel = [];

supprime toute la liste de tableaux $ scope.yourModel que 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 pourra aider.

user3856437
la source