AngularJS: lista powtórzeń ng nie jest aktualizowana, gdy element modelu jest łączony z tablicy modelu

100

Mam dwa kontrolery i udostępniam dane między nimi za pomocą funkcji app.factory.

Pierwszy kontroler dodaje widżet do tablicy modelu (pluginsDisplayed) po kliknięciu łącza. Widżet jest wstawiany do tablicy i ta zmiana jest odzwierciedlana w widoku (który używa ng-repeat do pokazania zawartości tablicy):

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

Widżet jest zbudowany na trzech dyrektywach: k2plugin, remove i resize. Dyrektywa remove dodaje rozpiętość do szablonu dyrektywy k2plugin. Kliknięcie tego zakresu powoduje usunięcie odpowiedniego elementu we współdzielonej tablicy za pomocą Array.splice(). Współdzielony szyk jest poprawnie aktualizowany, ale zmiana nie jest odzwierciedlana w widoku. Jednak po dodaniu innego elementu, po usunięciu, widok jest poprawnie odświeżany, a poprzednio usunięty element nie jest wyświetlany.

Co się mylę? Czy możesz mi wyjaśnić, dlaczego to nie działa? Czy jest lepszy sposób na zrobienie tego, co próbuję zrobić z AngularJS?

To jest mój 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>

To jest mój 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);
        })
    };
});
janesconference
źródło

Odpowiedzi:

131

Za każdym razem, gdy wykonujesz jakąś operację poza AngularJS, taką jak wywołanie Ajax z jQuery lub wiązanie zdarzenia z elementem takim jak tutaj, musisz powiadomić AngularJS o aktualizacji. Oto zmiana kodu, którą musisz zrobić:

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

Oto dokumentacja na ten temat: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

Mathew Berg
źródło
4
Rozważ przeniesienie scope.remove (element) i scope.resize (elementu) wewnątrz wyrażenia / funkcji przekazanej do $ apply.
Per Hornshøj-Schierbeck
1
@ PerHornshøj-Schierbeck Zgadzam się, w przeciwnym razie firma Angular nie będzie świadoma błędów, które wystąpią.
Jim Aho
2
bądź ostrożny ! normalnie Angular wywołuje cykl podsumowania, kiedy musi, a $ apply wywoła go ręcznie. Wykonywanie tego ręcznie często jest złą praktyką, ponieważ możemy popełnić błędy optymalizacji, a to może pochłaniać zasoby.
Alex
Nie rozumiem, co to za usunięcie lub zmiana rozmiaru, który tam umieszczasz.
Desarrollo Desafio de Guerrero
53

Jeśli dodasz $scope.$apply();po prawej, $scope.pluginsDisplayed.splice(index,1);to działa.

Nie jestem pewien, dlaczego tak się dzieje, ale w zasadzie, gdy AngularJS nie wie, że $ scope się zmienił, wymaga ręcznego wywołania $ apply. Jestem też nowy w AngularJS, więc nie mogę tego lepiej wyjaśnić. Muszę też bardziej się temu przyjrzeć.

Znalazłem ten niesamowity artykuł, który wyjaśnia to całkiem poprawnie. Uwaga: myślę, że lepiej byłoby użyć ng-click (docs) niż wiązać się z „mousedown”. Napisałem tutaj prostą aplikację ( http://avinash.me/losh , źródło http://github.com/hardfire/losh ) opartą na AngularJS. Nie jest zbyt czysty, ale może być pomocny.

avk
źródło
7

Miałem ten sam problem. Problem polegał na tym, że „ng-controller” został zdefiniowany dwukrotnie (w routingu, a także w HTML).

shreedhar bhat
źródło
1

Usuń „utwór po indeksie” z powtórzenia ng i odświeży DOM

Bassem Zaitoun
źródło
0

Jest na to łatwy sposób. Bardzo łatwe. Odkąd to zauważyłem

$scope.yourModel = [];

usuwa całą listę tablic $ scope.yourModel, którą możesz zrobić w ten sposób

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 zostanie zaktualizowany za pomocą clonedObjects.

Mam nadzieję, że to pomoże.

user3856437
źródło