Підтримуйте модель сфери застосування при переході між поглядами в AngularJS


152

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

Як би я переконався, що значення, введені у вигляді view1 , не будуть скинуті, коли користувач залишає і повертається до view1 ?

Що я маю на увазі, як другий візит до view1 може підтримувати такий самий стан моделі, як я залишив її?

Відповіді:


174

У мене було небагато часу, щоб розібратися, який найкращий спосіб зробити це. Я також хотів зберегти стан, коли користувач залишає сторінку та натискає кнопку назад, щоб повернутися до старої сторінки; а не просто вносити всі мої дані в кореневу зону .

Кінцевим результатом є надання сервісу для кожного контролера . У контролері у вас просто є функції та змінні, про які ви не дбаєте, якщо вони будуть очищені.

Служба для контролера вводиться шляхом введення залежності. Оскільки служби є одинаковими, їх дані не знищуються, як дані в контролері.

У сервісі у мене є модель. модель САМО має дані - немає функцій -. Таким чином він може бути перетворений назад і назад від JSON, щоб зберегти його. Я використовував html5 localstorage для наполегливості.

Нарешті, я використав window.onbeforeunloadі $rootScope.$broadcast('saveState');дав всім службам знати, що вони повинні зберегти свій стан, і $rootScope.$broadcast('restoreState')повідомити їм про відновлення свого стану (використовується для того, коли користувач залишає сторінку та натискає кнопку назад, щоб повернутися на сторінку відповідно).

Приклад служби під назвою userService для мого користувачаController :

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
воно зберігається в різних переглядах, точно так само, як ви запропонували, зберігайте його в сервісі. Що цікаво, він також зберігається, коли сторінка змінить так само, як ви пропонуєте з вікном. спасибі за те, що ви зробили подвійну перевірку. цій відповіді більше року, і все ще здається, що це найпростіший спосіб робити речі з кутом.
Антон

Дякую, що ти повернувся сюди, навіть якщо це старе :). Впевнені, що ваше рішення охоплює і перегляди, і сторінки, але не пояснює, який код виконує яку мету, і він недостатньо повний, щоб його можна було використовувати . Я просто попереджав читачів, щоб уникнути подальших питань . Було б чудово, якби ви встигли внести зміни, щоб покращити свою відповідь.
Хуго Вуд

Що робити , якщо в контролері замість цього: у $scope.user = userService;вас є що - щось на зразок цього: $scope.name = userService.model.name; $scope.email = userService.model.email;. Чи буде вона працювати або зміна змінних у виду не змінить її в Сервісі?
спорт

2
я думаю, що кутовий не має вбудованого механізму для такої звичайної речі
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); });
Shivendra

10

$ rootScope - велика глобальна змінна, яка добре підходить для разових речей або невеликих додатків. Скористайтеся послугою, якщо ви хочете інкапсулювати свою модель та / або поведінку (а можливо, повторно використовувати її в іншому місці). Крім публікації згаданої ОП у групі Google, див. Також https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion .


8

Кутовий насправді не забезпечує те, що ви шукаєте, поза коробкою. Що я б зробив, щоб виконати те, що ти шукаєш, це використовувати наступні додатки

Маршрутизатор та додатковий інтерфейс маршрутизатора

Ці два нададуть вам маршрутизацію та липкі стани, ви можете вкладати вкладки між станами, і вся інформація буде збережена, оскільки сфера "залишається живою", так би мовити.

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


Я читав документи, але не міг знайти, як зберегти введення форми, коли користувач натискає кнопку повернення браузера. Чи можете ви вказати мені на конкретику, будь ласка? Дякую!
Далібор

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, як локальне зберігання та інші речі).


так, працював як шарм. Наша фабрика даних вже була закодована, і я використовував синтаксис ControllerAs, тому я переписав її на контролер $ range, щоб 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;
...

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

Вставте службу RestoScope у свій контролер (приклад нижче):

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

Наведений вище приклад ініціалізує $ range.user до збереженого значення, інакше буде за замовчуванням задане значення та збереже це. Якщо сторінку закрити, оновити або змінити маршрут, поточні значення всіх зареєстрованих змінних областей будуть збережені та будуть відновлені наступного разу, коли маршрут / сторінку буде відвідуватися.


Коли я додав це до свого коду, я отримую помилку "TypeError: Не вдається прочитати властивість" locals "невизначеного" Як це виправити? @brett
Michael

Ви використовуєте Angular для обробки своїх маршрутів? Я здогадуюсь "ні", оскільки $ route.current виявляється невизначеним.
Бретт Пеннінгз

Так, кут обробляє мою маршрутизацію. Ось зразок того, що я маю в маршрутизації: JBenchApp.config (['$ routeProvider', '$ locationProvider', функція ($ routeProvider, $ locationProvider) {$ routeProvider. When ('/ dashboard', {templateUrl: ' partials / dashboard.html ', контролер:' JBenchCtrl '})
Michael Mahony

Моя інша здогадка - це те, що ваш контролер працює перед налаштуванням програми. У будь-якому випадку, ви, ймовірно, могли б обійти проблему, скориставшись $ location замість $ route, а потім передавши в області $ range від контролера, замість того, щоб отримати доступ до нього в маршруті. Спробуйте скористатись gist.github.com/brettpennings/ae5d34de0961eef010c6 для своєї служби замість цього і передайте в $ range як ваш третій параметр RestoScope.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>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.