Behalten Sie das Bereichsmodell bei, wenn Sie in AngularJS zwischen Ansichten wechseln

152

Ich lerne AngularJS. Angenommen , ich habe / view1 mit My1Ctrl und / view2 mit My2Ctrl . Dies kann mithilfe von Registerkarten navigiert werden, auf denen jede Ansicht ihre eigene einfache, aber unterschiedliche Form hat.

Wie kann ich sicherstellen, dass die in Form von view1 eingegebenen Werte nicht zurückgesetzt werden, wenn ein Benutzer a verlässt und dann zu view1 zurückkehrt ?

Was ich meine ist, wie kann der zweite Besuch von view1 genau den Status des Modells beibehalten , den ich verlassen habe?

JeremyWeir
quelle

Antworten:

174

Ich habe mir ein bisschen Zeit genommen, um herauszufinden, wie das am besten geht. Ich wollte auch den Status beibehalten, wenn der Benutzer die Seite verlässt und dann die Zurück-Taste drückt, um zur alten Seite zurückzukehren. und nicht nur alle meine Daten in das Rootscope legen .

Das Endergebnis ist ein Service für jeden Controller . In der Steuerung haben Sie nur Funktionen und Variablen, die Sie nicht interessieren, wenn sie gelöscht werden.

Der Dienst für die Steuerung wird durch Abhängigkeitsinjektion injiziert. Da Dienste Singletons sind, werden ihre Daten nicht wie die Daten in der Steuerung zerstört.

Im Service habe ich ein Modell. Das Modell hat NUR Daten - keine Funktionen -. Auf diese Weise kann es von JSON hin und her konvertiert werden, um es beizubehalten. Ich habe den HTML5-Localstorage für die Persistenz verwendet.

Zuletzt habe ich verwendet window.onbeforeunloadund $rootScope.$broadcast('saveState');alle Dienste wissen lassen, dass sie ihren Status speichern sollen, und $rootScope.$broadcast('restoreState')um sie wissen zu lassen , dass sie ihren Status wiederherstellen sollen (wird verwendet, wenn der Benutzer die Seite verlässt und die Zurück-Taste drückt, um jeweils zur Seite zurückzukehren).

Beispieldienst namens userService für meinen userController :

app.factory('userService', ['$rootScope', function ($rootScope) {

    var service = {

        model: {
            name: '',
            email: ''
        },

        SaveState: function () {
            sessionStorage.userService = angular.toJson(service.model);
        },

        RestoreState: function () {
            service.model = angular.fromJson(sessionStorage.userService);
        }
    }

    $rootScope.$on("savestate", service.SaveState);
    $rootScope.$on("restorestate", service.RestoreState);

    return service;
}]);

userController Beispiel

function userCtrl($scope, userService) {
    $scope.user = userService;
}

Die Ansicht verwendet dann eine solche Bindung

<h1>{{user.model.name}}</h1>

Und im App-Modul kümmere ich mich innerhalb der Ausführungsfunktion um die Übertragung von saveState und restoreState

$rootScope.$on("$routeChangeStart", function (event, next, current) {
    if (sessionStorage.restorestate == "true") {
        $rootScope.$broadcast('restorestate'); //let everything know we need to restore state
        sessionStorage.restorestate = false;
    }
});

//let everthing know that we need to save state now.
window.onbeforeunload = function (event) {
    $rootScope.$broadcast('savestate');
};

Wie ich bereits erwähnte, dauerte es eine Weile, bis dieser Punkt erreicht war. Es ist eine sehr saubere Art, dies zu tun, aber es ist ein gutes Stück Technik, etwas zu tun, von dem ich vermuten würde, dass es ein sehr häufiges Problem bei der Entwicklung in Angular ist.

Ich würde es gerne einfacher sehen, aber als saubere Möglichkeit, den Status zwischen den Controllern beizubehalten, auch wenn der Benutzer die Seite verlässt und zur Seite zurückkehrt.

Anton
quelle
10
Hier wird beschrieben, wie Daten beim Aktualisieren der Seite beibehalten werden und nicht, wenn die Route geändert wird, sodass die Frage nicht beantwortet wird. Außerdem funktioniert es nicht für Apps, die keine Routen verwenden. Außerdem wird nicht angezeigt, wo eingestellt sessionStorage.restoreStateist "true". Eine korrekte Lösung zum Beibehalten von Daten beim Aktualisieren der Seite finden Sie hier . Informationen zu dauerhaften Daten, wenn sich die Route ändert, finden Sie in der Antwort von carloscarcamo. Sie müssen lediglich die Daten in einen Dienst stellen.
Hugo Wood
2
Es bleibt in allen Ansichten bestehen, genau so, wie Sie es vorschlagen. Speichern Sie es in einem Dienst. Interessanterweise bleibt es auch bei einer Seitenänderung bestehen, so wie Sie es mit dem window.onbeforeunload vorgeschlagen haben. Danke, dass du mich dazu gebracht hast, es noch einmal zu überprüfen. Diese Antwort ist über ein Jahr alt und scheint immer noch der einfachste Weg zu sein, Dinge mit Winkel zu tun.
Anton
Danke, dass du hierher zurückgekommen bist, obwohl es alt ist :). Sicher, Ihre Lösung deckt sowohl Ansichten als auch Seiten ab, erklärt jedoch nicht, welcher Code welchem ​​Zweck dient, und er ist nicht vollständig genug, um verwendet werden zu können . Ich habe die Leser lediglich gewarnt, weitere Fragen zu vermeiden . Es wäre großartig, wenn Sie Zeit hätten, Änderungen vorzunehmen, um Ihre Antwort zu verbessern.
Hugo Wood
Was ist, wenn in Ihrem Controller stattdessen: $scope.user = userService;Sie haben so etwas : $scope.name = userService.model.name; $scope.email = userService.model.email;. Funktioniert es oder ändert sich das Ändern der angezeigten Variablen im Service nicht?
Sport
2
Ich denke, dass Winkel einen eingebauten Mechanismus für eine solche gemeinsame Sache
fehlt
22

Ein bisschen spät für eine Antwort, aber gerade aktualisiert Geige mit einigen Best Practice

jsfiddle

var myApp = angular.module('myApp',[]);
myApp.factory('UserService', function() {
    var userService = {};

    userService.name = "HI Atul";

    userService.ChangeName = function (value) {

       userService.name = value;
    };

    return userService;
});

function MyCtrl($scope, UserService) {
    $scope.name = UserService.name;
    $scope.updatedname="";
    $scope.changeName=function(data){
        $scope.updateServiceName(data);
    }
    $scope.updateServiceName = function(name){
        UserService.ChangeName(name);
        $scope.name = UserService.name;
    }
}
Atul Chaudhary
quelle
Ich muss dies sogar zwischen Seitennavigationen tun (keine Seitenaktualisierungen). ist das richtig?
FutuToad
Ich denke, ich sollte den Teil hinzufügen, um diese Antwort zu vervollständigen, der updateServiceaufgerufen werden sollte, wenn der Controller zerstört wird - $scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
Shivendra
10

$ rootScope ist eine große globale Variable, die für einmalige Dinge oder kleine Apps geeignet ist. Verwenden Sie einen Dienst, wenn Sie Ihr Modell und / oder Verhalten kapseln möchten (und es möglicherweise an anderer Stelle wiederverwenden möchten). Neben dem erwähnten Google-Gruppenbeitrag siehe OP auch https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion .

Mark Rajcok
quelle
8

Angular bietet nicht wirklich das, wonach Sie suchen. Was ich tun würde, um das zu erreichen, wonach Sie suchen, ist die Verwendung der folgenden Add-Ons

UI Router & UI Router Extras

Diese beiden bieten Ihnen zustandsbasiertes Routing und Sticky-Status. Sie können zwischen Status wechseln und alle Informationen werden gespeichert, wenn der Bereich sozusagen "am Leben bleibt".

Überprüfen Sie die Dokumentation zu beiden, da es ziemlich einfach ist. Die Extras des UI-Routers zeigen auch gut , wie Sticky States funktionieren.

Joshua Kelly
quelle
Ich habe die Dokumente gelesen, konnte jedoch keine Formulareingaben beibehalten, wenn der Benutzer auf die Schaltfläche "Zurück" des Browsers klickt. Können Sie mich bitte auf die Einzelheiten hinweisen? Vielen Dank!
Dalibor
6

Ich hatte das gleiche Problem. Folgendes habe ich getan: Ich habe ein SPA mit mehreren Ansichten auf derselben Seite (ohne Ajax). Dies ist also der Code des Moduls:

var app = angular.module('otisApp', ['chieffancypants.loadingBar', 'ngRoute']);

app.config(['$routeProvider', function($routeProvider){
    $routeProvider.when('/:page', {
        templateUrl: function(page){return page.page + '.html';},
        controller:'otisCtrl'
    })
    .otherwise({redirectTo:'/otis'});
}]);

Ich habe nur einen Controller für alle Ansichten, aber das Problem ist das gleiche wie die Frage, der Controller aktualisiert immer Daten. Um dieses Verhalten zu vermeiden, habe ich die oben vorgeschlagenen Schritte ausgeführt und einen Dienst für diesen Zweck erstellt und dann übergeben an die Steuerung wie folgt:

app.factory('otisService', function($http){
    var service = {            
        answers:[],
        ...

    }        
    return service;
});

app.controller('otisCtrl', ['$scope', '$window', 'otisService', '$routeParams',  
function($scope, $window, otisService, $routeParams){        
    $scope.message = "Hello from page: " + $routeParams.page;
    $scope.update = function(answer){
        otisService.answers.push(answers);
    };
    ...
}]);

Jetzt kann ich die Aktualisierungsfunktion aus jeder meiner Ansichten aufrufen, Werte übergeben und mein Modell aktualisieren. Ich muss keine HTML5-APIs für Persistenzdaten verwenden (dies ist in meinem Fall möglicherweise in anderen Fällen erforderlich, um HTML5 zu verwenden apis wie localstorage und andere Sachen).

carloscarcamo
quelle
Ja, wirkte wie ein Zauber. Unsere Datenfactory wurde bereits codiert und ich habe die ControllerAs-Syntax verwendet. Daher habe ich sie in $ scope controller umgeschrieben, damit Angular sie steuern kann. Die Implementierung war sehr einfach. Dies ist die allererste eckige App, die ich schreibe. tks
egidiocs
2

Eine Alternative zu Diensten ist die Verwendung des Wertspeichers.

In der Basis meiner App habe ich dies hinzugefügt

var agentApp = angular.module('rbAgent', ['ui.router', 'rbApp.tryGoal', 'rbApp.tryGoal.service', 'ui.bootstrap']);

agentApp.value('agentMemory',
    {
        contextId: '',
        sessionId: ''
    }
);
...

Und dann verweise ich in meinem Controller nur auf den Wertspeicher. Ich denke nicht, dass es etwas bringt, wenn der Benutzer den Browser schließt.

angular.module('rbAgent')
.controller('AgentGoalListController', ['agentMemory', '$scope', '$rootScope', 'config', '$state', function(agentMemory, $scope, $rootScope, config, $state){

$scope.config = config;
$scope.contextId = agentMemory.contextId;
...
Lawrie R.
quelle
1

Lösung, die für mehrere Bereiche und mehrere Variablen in diesen Bereichen funktioniert

Dieser Service basiert auf Antons Antwort, ist jedoch erweiterbarer und funktioniert über mehrere Bereiche hinweg und ermöglicht die Auswahl mehrerer Bereichsvariablen im selben Bereich. Es verwendet den Routenpfad, um jeden Bereich zu indizieren, und dann die Namen der Bereichsvariablen, um eine Ebene tiefer zu indizieren.

Erstellen Sie einen Dienst mit diesem Code:

angular.module('restoreScope', []).factory('restoreScope', ['$rootScope', '$route', function ($rootScope, $route) {

    var getOrRegisterScopeVariable = function (scope, name, defaultValue, storedScope) {
        if (storedScope[name] == null) {
            storedScope[name] = defaultValue;
        }
        scope[name] = storedScope[name];
    }

    var service = {

        GetOrRegisterScopeVariables: function (names, defaultValues) {
            var scope = $route.current.locals.$scope;
            var storedBaseScope = angular.fromJson(sessionStorage.restoreScope);
            if (storedBaseScope == null) {
                storedBaseScope = {};
            }
            // stored scope is indexed by route name
            var storedScope = storedBaseScope[$route.current.$$route.originalPath];
            if (storedScope == null) {
                storedScope = {};
            }
            if (typeof names === "string") {
                getOrRegisterScopeVariable(scope, names, defaultValues, storedScope);
            } else if (Array.isArray(names)) {
                angular.forEach(names, function (name, i) {
                    getOrRegisterScopeVariable(scope, name, defaultValues[i], storedScope);
                });
            } else {
                console.error("First argument to GetOrRegisterScopeVariables is not a string or array");
            }
            // save stored scope back off
            storedBaseScope[$route.current.$$route.originalPath] = storedScope;
            sessionStorage.restoreScope = angular.toJson(storedBaseScope);
        },

        SaveState: function () {
            // get current scope
            var scope = $route.current.locals.$scope;
            var storedBaseScope = angular.fromJson(sessionStorage.restoreScope);

            // save off scope based on registered indexes
            angular.forEach(storedBaseScope[$route.current.$$route.originalPath], function (item, i) {
                storedBaseScope[$route.current.$$route.originalPath][i] = scope[i];
            });

            sessionStorage.restoreScope = angular.toJson(storedBaseScope);
        }
    }

    $rootScope.$on("savestate", service.SaveState);

    return service;
}]);

Fügen Sie diesen Code Ihrer Ausführungsfunktion in Ihrem App-Modul hinzu:

$rootScope.$on('$locationChangeStart', function (event, next, current) {
    $rootScope.$broadcast('savestate');
});

window.onbeforeunload = function (event) {
    $rootScope.$broadcast('savestate');
};

Injizieren Sie den restoreScope-Dienst in Ihren Controller (Beispiel unten):

function My1Ctrl($scope, restoreScope) {
    restoreScope.GetOrRegisterScopeVariables([
         // scope variable name(s)
        'user',
        'anotherUser'
    ],[
        // default value(s)
        { name: 'user name', email: '[email protected]' },
        { name: 'another user name', email: '[email protected]' }
    ]);
}

Im obigen Beispiel wird $ scope.user mit dem gespeicherten Wert initialisiert, andernfalls wird standardmäßig der angegebene Wert verwendet und dieser gespeichert. Wenn die Seite geschlossen, aktualisiert oder die Route geändert wird, werden die aktuellen Werte aller registrierten Bereichsvariablen gespeichert und beim nächsten Besuch der Route / Seite wiederhergestellt.

Brett Pennings
quelle
Wenn ich dies zu meinem Code hinzufüge, wird die Fehlermeldung "TypeError: Eigenschaft 'local' von undefined kann nicht gelesen werden" angezeigt. Wie behebe ich das? @brett
Michael Mahony
Verwenden Sie Angular, um Ihre Routen zu verwalten? Ich vermute "nein", da $ route.current undefiniert zu sein scheint.
Brett Pennings
Ja, eckig kümmert sich um mein Routing. Hier ist ein Beispiel für das, was ich im Routing habe: JBenchApp.config (['$ routeProvider', '$ locationProvider', Funktion ($ routeProvider, $ locationProvider) {$ routeProvider. When ('/ dashboard', {templateUrl: ' partials / dashboard.html ', Controller:' JBenchCtrl '})
Michael Mahony
Meine einzige andere Vermutung ist, dass Ihr Controller ausgeführt wird, bevor Ihre App konfiguriert wird. In beiden Fällen könnten Sie das Problem wahrscheinlich umgehen, indem Sie $ location anstelle von $ route verwenden und dann $ scope vom Controller übergeben, anstatt auf die Route zuzugreifen. Versuchen Sie stattdessen, gist.github.com/brettpennings/ae5d34de0961eef010c6 für Ihren Dienst zu verwenden, und übergeben Sie $ scope als dritten Parameter von restoreScope.GetOrRegisterScopeVariables in Ihrem Controller. Wenn das für Sie funktioniert, mache ich das vielleicht zu meiner neuen Antwort. Viel Glück!
Brett Pennings
1

Mit $locationChangeStartevent können Sie den vorherigen Wert in $rootScopeoder in einem Service speichern . Wenn Sie zurückkommen, initialisieren Sie einfach alle zuvor gespeicherten Werte. Hier ist eine kurze Demo mit $rootScope.

Geben Sie hier die Bildbeschreibung ein

var app = angular.module("myApp", ["ngRoute"]);
app.controller("tab1Ctrl", function($scope, $rootScope) {
    if ($rootScope.savedScopes) {
        for (key in $rootScope.savedScopes) {
            $scope[key] = $rootScope.savedScopes[key];
        }
    }
    $scope.$on('$locationChangeStart', function(event, next, current) {
        $rootScope.savedScopes = {
            name: $scope.name,
            age: $scope.age
        };
    });
});
app.controller("tab2Ctrl", function($scope) {
    $scope.language = "English";
});
app.config(function($routeProvider) {
    $routeProvider
        .when("/", {
            template: "<h2>Tab1 content</h2>Name: <input ng-model='name'/><br/><br/>Age: <input type='number' ng-model='age' /><h4 style='color: red'>Fill the details and click on Tab2</h4>",
            controller: "tab1Ctrl"
        })
        .when("/tab2", {
            template: "<h2>Tab2 content</h2> My language: {{language}}<h4 style='color: red'>Now go back to Tab1</h4>",
            controller: "tab2Ctrl"
        });
});
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular-route.js"></script>
<body ng-app="myApp">
    <a href="#/!">Tab1</a>
    <a href="#!tab2">Tab2</a>
    <div ng-view></div>
</body>
</html>

Hari Das
quelle