Поддерживать модель области видимости при переключении между представлениями в AngularJS

152

Я изучаю AngularJS. Допустим, у меня есть / view1, используя My1Ctrl , и / view2, использующий My2Ctrl ; к нему можно перейти с помощью вкладок, где каждое представление имеет свою простую, но различную форму.

Как бы убедиться, что значения введены в виде view1 , не сбрасываются, когда пользователь покидает и затем возвращается в view1 ?

Я имею в виду, как во время второго посещения view1 можно сохранить то же состояние модели, в котором я ее оставил?

JeremyWeir
источник

Ответы:

174

Я потратил немного времени, чтобы понять, как это лучше всего сделать. Я также хотел сохранить состояние, когда пользователь покидает страницу и затем нажимает кнопку «Назад», чтобы вернуться на старую страницу; а не просто положить все мои данные в корневой каталог .

Конечный результат - иметь сервис для каждого контроллера . В контроллере просто есть функции и переменные, которые вам не нужны, если они очищены.

Служба для контроллера вводится путем внедрения зависимостей. Поскольку сервисы являются одиночными, их данные не уничтожаются, как данные в контроллере.

В сервисе у меня есть модель. модель ТОЛЬКО имеет данные - никаких функций -. Таким образом, он может быть преобразован назад и вперед из JSON, чтобы сохранить его. Я использовал html5 localalstorage для настойчивости.

Наконец, я использовал window.onbeforeunloadи, $rootScope.$broadcast('saveState');чтобы все службы знали, что они должны сохранить свое состояние, и $rootScope.$broadcast('restoreState')чтобы они знали, чтобы восстановить свое состояние (используется, когда пользователь покидает страницу и нажимает кнопку «Назад», чтобы вернуться на страницу соответственно).

Пример сервиса под названием userService для моего 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

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

Затем представление использует привязку, как это

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

А в модуле приложения , в функции запуска я обрабатывать вещание в saveState и 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');
};

Как я уже говорил, это заняло некоторое время, чтобы прийти к этой точке. Это очень чистый способ сделать это, но это довольно трудоемкая разработка, чтобы сделать что-то, что, как я подозреваю, является очень распространенной проблемой при разработке в Angular.

Я хотел бы видеть проще, но как чистые способы обработки состояния на контроллерах, в том числе когда пользователь уходит и возвращается на страницу.

Антон
источник
10
Здесь описывается, как сохранить данные при обновлении страницы, а не как сохранить их при изменении маршрута, чтобы он не отвечал на вопрос. Плюс это не будет работать для приложений, которые не используют маршруты. Плюс это не показывает, где sessionStorage.restoreStateустановлено "true". Чтобы найти правильное решение для сохранения данных при обновлении страницы, посмотрите здесь . Для получения постоянных данных при изменении маршрута посмотрите ответ Карлоскаркамо. Все, что вам нужно, это поместить данные в сервис.
Хьюго Вуд
2
он сохраняется при просмотре, точно так же, как вы предлагаете, хранить его в сервисе. Интересно, что он сохраняется и при смене страницы так же, как вы предлагаете с помощью window.onbeforeunload. спасибо, что заставил меня проверить еще раз. этому ответу больше года, и он все еще кажется самым простым способом сделать что-то с помощью angular.
Антон
Спасибо, что пришли сюда, хотя он старый :). Конечно, ваше решение охватывает как представления, так и страницы, но не объясняет, какой код служит для какой цели, и оно недостаточно полно, чтобы его можно было использовать . Я просто предупреждал читателей, чтобы избежать дальнейших вопросов . Было бы здорово, если бы у вас было время внести изменения, чтобы улучшить свой ответ.
Хьюго Вуд
Что делать , если в контроллере вместо этого: у $scope.user = userService;вас есть что - то вроде этого: $scope.name = userService.model.name; $scope.email = userService.model.email;. Будет ли это работать или изменение переменных в представлении не изменит это в Сервисе?
спорт
2
я думаю, что angular не хватает встроенного механизма для такой обычной вещи
obe6
22

Немного опоздал с ответом, но только что обновил скрипку с некоторыми лучшими практиками

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;
    }
}
Атул Чаудхари
источник
Я даже должен сделать это между переходами по страницам (не обновления страницы). это верно?
FutuToad
Я думаю, что я должен добавить часть, чтобы завершить этот ответ, который updateServiceдолжен быть вызван, когда контроллер разрушен - $scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
Шивендра
10

$ rootScope - это большая глобальная переменная, которая подходит для одноразовых вещей или небольших приложений. Используйте сервис, если вы хотите инкапсулировать вашу модель и / или поведение (и, возможно, использовать его в другом месте). В дополнение к сообщению группы Google, о котором упоминается OP, см. Также https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion .

Марк Райкок
источник
8

Angular на самом деле не обеспечивает то, что вы ищете из коробки. То, что я хотел бы сделать, чтобы добиться того, что вы хотите, это использовать следующие дополнения

UI Router и UI Router Дополнительно

Эти два предоставляют вам маршрутизацию на основе состояний и фиксированные состояния, вы можете переключаться между состояниями, и вся информация будет сохраняться, так как область, так сказать, «остается в живых».

Посмотрите документацию по обоим, так как это довольно просто, дополнения к пользовательскому интерфейсу маршрутизатора также хорошо демонстрируют, как работают закрепленные состояния.

Джошуа Келли
источник
Я прочитал документы, но не смог найти, как сохранить входные данные формы, когда пользователь нажимает кнопку возврата браузера. Можете ли вы указать мне на детали, пожалуйста? Спасибо!
Далибор
6

У меня была та же проблема, вот что я сделал: у меня есть SPA с несколькими представлениями на одной странице (без ajax), так что это код модуля:

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'});
}]);

У меня есть только один контроллер для всех представлений, но проблема та же, что и в вопросе, контроллер всегда обновляет данные, чтобы избежать такого поведения, я сделал то, что люди предложили выше, и создал сервис для этой цели, а затем передал его к контроллеру следующим образом:

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);
    };
    ...
}]);

Теперь я могу вызывать функцию обновления из любого из моих представлений, передавать значения и обновлять мою модель, мне не нужно использовать html5 apis для постоянных данных (это в моем случае, возможно, в других случаях необходимо будет использовать html5 apis, как localalstorage и другие вещи).

carloscarcamo
источник
да, работал как шарм. Наша фабрика данных уже была закодирована, и я использовал синтаксис ControllerAs, поэтому я переписал его на $ scope controller, чтобы Angular мог его контролировать. Реализация была очень простой. Это самое первое угловое приложение, которое я пишу. tks
egidiocs
2

Альтернативой услугам является использование хранилища значений.

В базе моего приложения я добавил это

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

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

И тогда в моем контроллере я просто ссылаюсь на хранилище значений. Я не думаю, что это имеет значение, если пользователь закрывает браузер.

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
источник
1

Решение, которое будет работать для нескольких областей и нескольких переменных в этих областях

Этот сервис основан на ответе Антона, но является более расширяемым и будет работать в нескольких областях и позволяет выбирать несколько переменных области в одной и той же области. Он использует путь маршрута для индексации каждой области, а затем имена переменных области для индексации на один уровень глубже.

Создать сервис с этим кодом:

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

Добавьте этот код к своей функции запуска в модуле приложения:

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

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

Вставьте службу restoreScope в ваш контроллер (пример ниже):

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

Приведенный выше пример инициализирует $ scope.user для сохраненного значения, в противном случае по умолчанию будет предоставлено значение и сохранит его. Если страница закрыта, обновлена ​​или маршрут изменен, текущие значения всех зарегистрированных переменных области будут сохранены и будут восстановлены при следующем посещении маршрута / страницы.

Бретт Пеннингс
источник
Когда я добавил это в свой код, я получаю сообщение об ошибке «Ошибка типа: не могу прочитать свойство« locals »из неопределенного» Как это исправить? @brett
Майкл Махони
Вы используете Angular для обработки ваших маршрутов? Я предполагаю "нет", так как $ route.current кажется неопределенным.
Бретт Пеннингс
Да, Angular управляет моей маршрутизацией. Вот пример того, что у меня есть в маршрутизации: JBenchApp.config (['$ routeProvider', '$ locationProvider', функция ($ routeProvider, $ locationProvider) {$ routeProvider. When ('/ dashboard', {templateUrl: ' partials / dashboard.html ', контроллер:' JBenchCtrl '})
Майкл Махони
Мое единственное предположение, что ваш контроллер работает до того, как ваше приложение будет настроено. В любом случае, вы могли бы обойти проблему, используя $ location вместо $ route и затем передавая $ scope из контроллера вместо доступа к нему в маршруте. Попробуйте вместо этого использовать gist.github.com/brettpennings/ae5d34de0961eef010c6 для своей службы и передайте $ scope в качестве третьего параметра restoreScope.GetOrRegisterScopeVariables в своем контроллере. Если это работает для вас, я мог бы просто сделать это моим новым ответом. Удачи!
Бретт Пеннингс
1

Вы можете использовать $locationChangeStartсобытие, чтобы сохранить предыдущее значение в $rootScopeили в сервисе. Когда вы вернетесь, просто инициализируйте все ранее сохраненные значения. Вот краткое демонстрационное использование $rootScope.

введите описание изображения здесь

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>

Хари Дас
источник