AngularJS: l'elenco ng-repeat non viene aggiornato quando un elemento del modello viene unito dall'array del modello


100

Ho due controller e condivido i dati tra di loro con una funzione app.factory.

Il primo controller aggiunge un widget nell'array del modello (pluginsDisplayed) quando si fa clic su un collegamento. Il widget viene inserito nell'array e questa modifica si riflette nella vista (che utilizza ng-repeat per mostrare il contenuto dell'array):

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

Il widget si basa su tre direttive, k2plugin, remove e resize. La direttiva remove aggiunge uno span al modello della direttiva k2plugin. Quando si fa clic su tale intervallo, l'elemento giusto nell'array condiviso viene eliminato con Array.splice(). L'array condiviso viene aggiornato correttamente, ma la modifica non si riflette nella visualizzazione. Tuttavia, quando viene aggiunto un altro elemento, dopo la rimozione, la vista viene aggiornata correttamente e l'elemento eliminato in precedenza non viene mostrato.

Cosa sto sbagliando? Mi spieghi perché non funziona? C'è un modo migliore per fare quello che sto cercando di fare con AngularJS?

Questo è il mio 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>

Questo è il mio 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);
        })
    };
});

Risposte:


131

Ogni volta che esegui una qualche forma di operazione al di fuori di AngularJS, come eseguire una chiamata Ajax con jQuery o associare un evento a un elemento come quello che hai qui, devi far sapere ad AngularJS di aggiornarsi. Ecco la modifica del codice che devi fare:

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

Ecco la documentazione su di esso: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply


4
Considera l'idea di spostare scope.remove (elemento) e scope.resize (elemento) all'interno di un'espressione / funzione passata a $ apply.
Per Hornshøj-Schierbeck

1
@ PerHornshøj-Schierbeck Sono d'accordo, altrimenti Angular non sarà a conoscenza degli errori se si verificano.
Jim Aho

2
stai attento ! normalmente, Angular chiama il ciclo digest quando deve e $ apply lo chiamerà manualmente. È spesso una cattiva pratica chiamarlo manualmente, perché possiamo commettere errori di ottimizzazione e può consumare risorse.
Alex

Non capisco cosa rimuovi o ridimensiona che stai inserendo.
Desarrollo Desafio de Guerrero

53

Se aggiungi un $scope.$apply();diritto dopo, $scope.pluginsDisplayed.splice(index,1);allora funziona.

Non sono sicuro del motivo per cui questo sta accadendo, ma fondamentalmente quando AngularJS non sa che $ scope è cambiato, richiede di chiamare $ apply manualmente. Sono anche nuovo su AngularJS, quindi non posso spiegarlo meglio. Anch'io ho bisogno di esaminarlo di più.

Ho trovato questo fantastico articolo che lo spiega abbastanza correttamente. Nota: penso che potrebbe essere meglio usare ng-click (docs) piuttosto che legare a "mousedown". Ho scritto una semplice app qui ( http://avinash.me/losh , fonte http://github.com/hardfire/losh ) basata su AngularJS. Non è molto pulito, ma potrebbe essere d'aiuto.


7

Ho avuto lo stesso problema. Il problema era perché "ng-controller" era stato definito due volte (nel routing e anche nell'HTML).



0

C'è un modo semplice per farlo. Molto facile. Da quando l'ho notato

$scope.yourModel = [];

rimuove tutto l'elenco di array $ scope.yourModel che puoi fare in questo modo

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

$ Scope.yourModel verrà aggiornato con clonedObjects.

Spero che aiuti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.