AngularJS: Die ng-repeat-Liste wird nicht aktualisiert, wenn ein Modellelement aus dem Modellarray gespleißt wird


100

Ich habe zwei Controller und teile Daten zwischen ihnen mit einer app.factory-Funktion.

Der erste Controller fügt dem Modellarray ein Widget hinzu (pluginsDisplayed), wenn auf einen Link geklickt wird. Das Widget wird in das Array verschoben und diese Änderung wird in die Ansicht übernommen (die ng-repeat verwendet, um den Array-Inhalt anzuzeigen):

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

Das Widget basiert auf drei Anweisungen: k2plugin, remove und resize. Die Direktive remove entfernt fügt der Vorlage der Direktive k2plugin eine Spanne hinzu. Wenn auf diesen Bereich geklickt wird, wird das richtige Element im freigegebenen Array mit gelöscht Array.splice(). Das freigegebene Array wird korrekt aktualisiert, die Änderung wird jedoch nicht in der Ansicht angezeigt. Wenn jedoch nach dem Entfernen ein weiteres Element hinzugefügt wird, wird die Ansicht korrekt aktualisiert und das zuvor gelöschte Element wird nicht angezeigt.

Was mache ich falsch? Können Sie mir erklären, warum das nicht funktioniert? Gibt es einen besseren Weg, um das zu tun, was ich mit AngularJS versuche?

Dies ist meine 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>

Dies ist meine 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);
        })
    };
});

Antworten:


131

Wenn Sie eine Operation außerhalb von AngularJS ausführen, z. B. einen Ajax-Aufruf mit jQuery ausführen oder ein Ereignis an ein Element wie das hier angegebene binden, müssen Sie AngularJS wissen lassen, dass es sich selbst aktualisieren soll. Hier ist die Codeänderung, die Sie vornehmen müssen:

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

Hier ist die Dokumentation dazu: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply


4
Ziehen Sie in Betracht, scope.remove (Element) und scope.resize (Element) in einen Ausdruck / eine Funktion zu verschieben, die an $ apply übergeben wird.
Per Hornshøj-Schierbeck

1
@ PerHornshøj-Schierbeck Ich stimme zu, andernfalls werden Angular keine Fehler bekannt, wenn diese auftreten.
Jim Aho

2
Achtung ! Normalerweise ruft Angular den Digest-Zyklus auf, wenn er muss, und $ apply ruft ihn manuell auf. Es ist oft eine schlechte Praxis, es manuell aufzurufen, da wir Optimierungsfehler machen können und es ressourcenintensiv sein kann.
Alex

Ich verstehe nicht, was das Entfernen oder Ändern der Größe ist, das Sie dort setzen.
Desarrollo Desafio de Guerrero

53

Wenn Sie $scope.$apply();gleich danach ein hinzufügen$scope.pluginsDisplayed.splice(index,1); danach , funktioniert es.

Ich bin nicht sicher, warum dies geschieht, aber wenn AngularJS nicht weiß, dass sich der $ -Bereich geändert hat, muss $ apply manuell aufgerufen werden. Ich bin auch neu bei AngularJS und kann dies nicht besser erklären. Ich muss auch mehr darüber nachdenken.

Ich habe diesen großartigen Artikel gefunden , der es ganz richtig erklärt. Hinweis: Ich denke, es ist möglicherweise besser, ng-click (docs) zu verwenden, als an "mousedown" zu binden. Ich habe hier eine einfache App geschrieben ( http://avinash.me/losh , Quelle http://github.com/hardfire/losh ), die auf AngularJS basiert. Es ist nicht sehr sauber, aber es könnte hilfreich sein.


7

Ich hatte das gleiche Problem. Das Problem war, dass 'ng-controller' zweimal definiert wurde (im Routing und auch im HTML).


1

Entfernen Sie "Spur nach Index" aus der ng-Wiederholung und es würde das DOM aktualisieren


0

Es gibt einen einfachen Weg, das zu tun. Sehr leicht. Da habe ich das bemerkt

$scope.yourModel = [];

Entfernt alle $ scope.yourModel-Array-Listen, die Sie so ausführen können

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

Das $ scope.yourModel wird mit den geklonten Objekten aktualisiert.

Hoffentlich hilft das.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.