Duy trì mô hình phạm vi khi thay đổi giữa các chế độ xem trong AngularJS


152

Tôi đang học AngularJS. Giả sử tôi có / view1 bằng My1Ctrl/ view2 bằng My2Ctrl ; có thể được điều hướng đến bằng cách sử dụng các tab trong đó mỗi chế độ xem có hình thức đơn giản nhưng khác nhau.

Làm cách nào để đảm bảo rằng các giá trị được nhập ở dạng view1 không được đặt lại, khi người dùng rời khỏi và sau đó quay lại view1 ?

Ý tôi là, làm thế nào lần truy cập thứ hai vào view1 có thể giữ chính xác trạng thái của mô hình khi tôi rời khỏi nó?

Câu trả lời:


174

Tôi đã dành một chút thời gian để tìm ra cách tốt nhất để làm điều này là gì. Tôi cũng muốn giữ trạng thái, khi người dùng rời khỏi trang và sau đó nhấn nút quay lại, để quay lại trang cũ; và không chỉ đưa tất cả dữ liệu của tôi vào rootscope .

Kết quả cuối cùng là có một dịch vụ cho mỗi bộ điều khiển . Trong bộ điều khiển, bạn chỉ có các hàm và biến mà bạn không quan tâm, nếu chúng bị xóa.

Dịch vụ cho bộ điều khiển được tiêm bằng cách tiêm phụ thuộc. Vì các dịch vụ là singletons, dữ liệu của chúng không bị hủy như dữ liệu trong bộ điều khiển.

Trong dịch vụ, tôi có một mô hình. mô hình CHỈ có dữ liệu - không có chức năng -. Bằng cách đó, nó có thể được chuyển đổi qua lại từ JSON để duy trì nó. Tôi đã sử dụng lưu trữ cục bộ html5 cho sự kiên trì.

Cuối cùng tôi đã sử dụng window.onbeforeunload$rootScope.$broadcast('saveState');để cho tất cả các dịch vụ biết rằng họ nên lưu trạng thái của mình và $rootScope.$broadcast('restoreState')để cho họ biết để khôi phục trạng thái của họ (được sử dụng khi người dùng rời khỏi trang và nhấn nút quay lại để quay lại trang tương ứng).

Dịch vụ ví dụ được gọi là userService cho userControll của tôi :

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 dụ

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

Chế độ xem sau đó sử dụng ràng buộc như thế này

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

Và trong mô-đun ứng dụng , trong phạm vi chức năng chạy i xử lý phát thanh truyền hình của saveStaterestoreState

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

Như tôi đã đề cập điều này phải mất một thời gian để đi đến điểm này. Đó là một cách rất sạch sẽ để làm điều đó, nhưng đó là một chút công bằng để làm một cái gì đó mà tôi nghi ngờ là một vấn đề rất phổ biến khi phát triển trong Angular.

Tôi rất muốn thấy dễ dàng hơn, nhưng là cách sạch để xử lý trạng thái trên các bộ điều khiển, kể cả khi người dùng rời khỏi và quay lại trang.


10
Phần này mô tả cách duy trì dữ liệu khi trang làm mới, không phải cách duy trì dữ liệu khi tuyến thay đổi, vì vậy nó không trả lời câu hỏi. Thêm vào đó, nó sẽ không hoạt động đối với các ứng dụng không sử dụng tuyến đường. Thêm vào đó, nó không hiển thị nơi sessionStorage.restoreStateđược đặt "true". Để có giải pháp chính xác để duy trì dữ liệu khi trang được làm mới, hãy xem tại đây . Để biết dữ liệu vẫn còn khi tuyến đường thay đổi, hãy xem câu trả lời của carloscarcamo. Tất cả bạn cần là đưa dữ liệu vào một dịch vụ.
Hugo Wood

2
nó vẫn tồn tại trên các lượt xem, giống hệt như cách bạn đề xuất, lưu trữ nó trong một dịch vụ. đủ thú vị, nó cũng tồn tại trên một trang thay đổi giống như cách bạn đề xuất với window.onb Beforeunload. cảm ơn vì đã khiến tôi kiểm tra lại Câu trả lời này đã hơn một năm tuổi và nó dường như vẫn là cách đơn giản nhất để làm mọi việc với góc cạnh.
Anton

Cảm ơn bạn đã quay trở lại đây, mặc dù nó đã cũ :). Chắc chắn giải pháp của bạn bao gồm cả lượt xem và trang, nhưng không giải thích mã nào phục vụ mục đích gì và nó không hoàn chỉnh đủ để có thể sử dụng được . Tôi chỉ đơn thuần là cảnh báo độc giả để tránh câu hỏi thêm . Sẽ thật tuyệt nếu bạn có thời gian để chỉnh sửa để cải thiện câu trả lời của mình.
Hugo Wood

Điều gì xảy ra nếu trong bộ điều khiển của bạn thay vì điều này: $scope.user = userService;bạn có một cái gì đó như thế này : $scope.name = userService.model.name; $scope.email = userService.model.email;. Nó sẽ hoạt động hay thay đổi các biến trong chế độ xem sẽ không thay đổi nó trong Dịch vụ?
thể thao

2
tôi nghĩ rằng góc cạnh thiếu một cơ chế tích hợp cho một thứ phổ biến như vậy
obe6

22

Một chút muộn cho một câu trả lời nhưng chỉ cần cập nhật fiddle với một số thực hành tốt nhất

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

Tôi thậm chí phải làm điều này giữa các điều hướng trang (không phải làm mới trang). điều này có đúng không
FutuToad

Tôi nghĩ rằng tôi nên thêm phần để hoàn thành câu trả lời này, updateServicenên được gọi khi bộ điều khiển bị phá hủy - $scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
Shivendra

10

$ rootScope là một biến toàn cầu lớn, rất tốt cho những thứ một lần hoặc các ứng dụng nhỏ. Sử dụng một dịch vụ nếu bạn muốn gói gọn mô hình và / hoặc hành vi của mình (và có thể sử dụng lại ở nơi khác). Ngoài nhóm google đăng bài OP được đề cập, xem thêm https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion .


8

Angular không thực sự cung cấp những gì bạn đang tìm kiếm ngoài hộp. Những gì tôi sẽ làm để thực hiện những gì bạn đang theo đuổi là sử dụng các tiện ích bổ sung sau

UI Router & UI Router Extras

Hai cái này sẽ cung cấp cho bạn các trạng thái dính và định tuyến dựa trên trạng thái, bạn có thể tab giữa các trạng thái và tất cả thông tin sẽ được lưu dưới dạng phạm vi "vẫn còn tồn tại".

Kiểm tra tài liệu trên cả hai vì nó khá dễ dàng, các bộ bổ sung bộ định tuyến ui cũng có một minh chứng tốt về cách thức hoạt động của các trạng thái dính.


Tôi đã đọc tài liệu, nhưng không thể tìm thấy cách bảo toàn đầu vào biểu mẫu khi người dùng nhấp vào nút quay lại trình duyệt. Bạn có thể chỉ cho tôi chi tiết cụ thể không? Cảm ơn!
Dalibor

6

Tôi đã có cùng một vấn đề, Đây là những gì tôi đã làm: Tôi có một SPA với nhiều lượt xem trong cùng một trang (không có ajax), vì vậy đây là mã của mô-đun:

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

Tôi chỉ có một bộ điều khiển cho tất cả các chế độ xem, nhưng, vấn đề cũng giống như câu hỏi, bộ điều khiển luôn làm mới dữ liệu, để tránh hành vi này, tôi đã làm những gì mọi người đề xuất ở trên và tôi đã tạo ra một dịch vụ cho mục đích đó, sau đó chuyển nó đến bộ điều khiển như sau:

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

Bây giờ tôi có thể gọi hàm cập nhật từ bất kỳ chế độ xem nào của mình, chuyển giá trị và cập nhật mô hình của mình, tôi không cần sử dụng apis html5 cho dữ liệu bền vững (đây là trường hợp của tôi, có thể trong trường hợp khác sẽ cần sử dụng html5 apis như lưu trữ cục bộ và những thứ khác).


vâng, làm việc như một lá bùa. Nhà máy dữ liệu của chúng tôi đã được mã hóa và tôi đang sử dụng cú pháp của Trình điều khiển, vì vậy tôi đã viết lại nó cho bộ điều khiển phạm vi $, vì vậy Angular có thể kiểm soát nó. Việc thực hiện rất đơn giản. Đây là ứng dụng góc cạnh đầu tiên mà tôi đang viết. tks
egidiocs

2

Một thay thế cho các dịch vụ là sử dụng các cửa hàng giá trị.

Trong cơ sở của ứng dụng của tôi, tôi đã thêm

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

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

Và sau đó trong bộ điều khiển của tôi, tôi chỉ tham chiếu các cửa hàng giá trị. Tôi không nghĩ nó có tác dụng nếu người dùng đóng trình duyệt.

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

$scope.config = config;
$scope.contextId = agentMemory.contextId;
...

1

Giải pháp sẽ hoạt động cho nhiều phạm vi và nhiều biến trong các phạm vi đó

Dịch vụ này dựa trên câu trả lời của Anton, nhưng có thể mở rộng hơn và sẽ hoạt động trên nhiều phạm vi và cho phép lựa chọn nhiều biến phạm vi trong cùng một phạm vi. Nó sử dụng đường dẫn để lập chỉ mục cho từng phạm vi và sau đó các tên biến phạm vi để lập chỉ mục sâu hơn một cấp.

Tạo dịch vụ với mã này:

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

Thêm mã này vào chức năng chạy của bạn trong mô-đun ứng dụng của bạn:

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

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

Tiêm dịch vụ restoreScope vào bộ điều khiển của bạn (ví dụ bên dưới):

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

Ví dụ trên sẽ khởi tạo $ scope.user thành giá trị được lưu trữ, nếu không sẽ mặc định với giá trị được cung cấp và lưu nó đi. Nếu trang bị đóng, được làm mới hoặc tuyến đường bị thay đổi, các giá trị hiện tại của tất cả các biến phạm vi đã đăng ký sẽ được lưu lại và sẽ được khôi phục vào lần tiếp theo của tuyến / trang.


Khi tôi thêm mã này vào mã của mình, tôi gặp lỗi "TypeError: Không thể đọc thuộc tính 'cục bộ' không xác định" Làm cách nào để khắc phục điều đó? @brett
Michael Mahony

Bạn đang sử dụng Angular để xử lý các tuyến đường của bạn? Tôi đoán "không" vì $ route.c hiện tại không xác định.
Brett Pennings

Vâng, góc đang xử lý định tuyến của tôi. Dưới đây là ví dụ về những gì tôi có trong định tuyến: JBenchApp.config (['$ routeProvider', '$ locationProvider', function ($ routeProvider, $ locationProvider) {$ routeProvider. Khi ('/ dashboard', {templateUrl: ' partials / dashboard.html ', trình điều khiển:' JBenchCtrl '})
Michael Mahony

Tôi chỉ đoán khác là bộ điều khiển của bạn đang chạy trước khi ứng dụng của bạn được cấu hình. Dù bằng cách nào, bạn có thể có thể khắc phục vấn đề bằng cách sử dụng $ location thay vì $ route và sau đó chuyển trong phạm vi $ từ bộ điều khiển thay vì truy cập nó trong tuyến. Hãy thử sử dụng gist.github.com/brettpennings/ae5d34de0961eef010c6 cho dịch vụ của bạn thay vào đó và chuyển trong phạm vi $ làm tham số thứ ba của restoreScope.GetOrRegisterScopeVariabled trong bộ điều khiển của bạn. Nếu điều này làm việc cho bạn, tôi có thể làm cho câu trả lời mới của tôi. Chúc may mắn!
Brett Pennings

1

Bạn có thể sử dụng $locationChangeStartsự kiện để lưu trữ giá trị trước đó trong $rootScopehoặc trong một dịch vụ. Khi bạn quay lại, chỉ cần khởi tạo tất cả các giá trị được lưu trữ trước đó. Đây là một bản demo nhanh bằng cách sử dụng $rootScope.

nhập mô tả hình ảnh ở đây

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>

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.