รักษาโมเดลขอบเขตเมื่อเปลี่ยนระหว่างมุมมองใน AngularJS


152

ฉันกำลังเรียนรู้ AngularJS สมมติว่าฉันมี/ view1ใช้My1Ctrlและ/ view2ใช้My2Ctrl ; ที่สามารถนำทางไปยังการใช้แท็บที่แต่ละมุมมองมีรูปแบบที่เรียบง่าย แต่แตกต่างกัน

ฉันจะให้แน่ใจว่าค่าที่ป้อนในรูปแบบของView1ยังไม่ได้ตั้งค่าใหม่เมื่อใบใช้แล้วกลับไปView1 ?

สิ่งที่ผมหมายถึงคือวิธีการที่สองจะสามารถView1ให้รัฐเดียวกันของรูปแบบที่แน่นอนตามที่ผมออกจากมันได้หรือไม่

คำตอบ:


174

ฉันใช้เวลาซักนิดเพื่อหาวิธีที่ดีที่สุดในการทำสิ่งนี้ ฉันต้องการรักษาสถานะเมื่อผู้ใช้ออกจากหน้าแล้วกดปุ่มย้อนกลับเพื่อกลับไปที่หน้าเก่า และไม่เพียง แต่นำข้อมูลทั้งหมดของฉันเข้าไปในrootscope

ผลสุดท้ายคือการมีบริการสำหรับแต่ละตัวควบคุม ในคอนโทรลเลอร์คุณเพียงแค่มีฟังก์ชั่นและตัวแปรที่คุณไม่สนใจถ้ามันถูกล้าง

บริการสำหรับตัวควบคุมถูกฉีดโดยการฉีดพึ่งพา เนื่องจากบริการเป็นแบบซิงเกิลข้อมูลของพวกเขาจะไม่ถูกทำลายเหมือนกับข้อมูลในคอนโทรลเลอร์

ในบริการฉันมีรูปแบบ โมเดลมีข้อมูลเท่านั้น - ไม่มีฟังก์ชั่น - ด้วยวิธีนี้สามารถแปลงไปมาจาก JSON เพื่อคงอยู่ ฉันใช้ hststorage html5 สำหรับการติดตา

สุดท้ายที่ฉันใช้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"สำหรับการแก้ปัญหาที่ถูกต้องที่จะยังคงมีข้อมูลเมื่อรีเฟรชหน้าให้ดูที่นี่ สำหรับข้อมูลที่คงอยู่เมื่อเส้นทางเปลี่ยนไปให้ดูที่คำตอบของ carloscarcamo สิ่งที่คุณต้องทำก็คือใส่ข้อมูลลงในบริการ
Hugo Wood

2
มันยังคงอยู่ในมุมมองในลักษณะเดียวกับที่คุณแนะนำเก็บไว้ในบริการ ที่น่าสนใจก็ยังคงมีอยู่ในหน้าเปลี่ยนแบบเดียวกับที่คุณแนะนำกับหน้าต่างก่อนที่จะโหลด ขอบคุณที่ทำให้ฉันตรวจสอบอีกครั้ง คำตอบนี้มีอายุมากกว่าหนึ่งปีและยังคงเป็นวิธีที่ง่ายที่สุดในการทำสิ่งต่าง ๆ ด้วยมุม
Anton

ขอบคุณที่กลับมาที่นี่แม้ว่ามันจะเก่าแล้วก็ตาม :) แน่นอนว่าโซลูชันของคุณครอบคลุมทั้งการดูและหน้า แต่ไม่ได้อธิบายว่ารหัสใดมีจุดประสงค์และไม่สมบูรณ์เพียงพอที่จะสามารถใช้งานได้ ฉันเป็นเพียงการเตือนผู้อ่านที่จะหลีกเลี่ยงคำถามเพิ่มเติม มันจะดีถ้าคุณมีเวลาทำการแก้ไขเพื่อปรับปรุงคำตอบของคุณ
Hugo Wood

เกิดอะไรขึ้นถ้าในการควบคุมของคุณแทนการนี้คุณมีบางสิ่งบางอย่างเช่นนี้$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 OP ดังกล่าวเห็นhttps://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion


8

เชิงมุมไม่ได้ให้สิ่งที่คุณต้องการอย่างแท้จริง สิ่งที่ฉันจะทำเพื่อให้บรรลุสิ่งที่คุณหลังจากนั้นใช้ส่วนเสริมต่อไปนี้

เราเตอร์ UIและอุปกรณ์เสริมเราเตอร์ UI

ทั้งสองนี้จะให้คุณกำหนดเส้นทางตามสถานะและสถานะเหนียวคุณสามารถแท็บระหว่างรัฐและข้อมูลทั้งหมดจะถูกบันทึกเป็นขอบเขต "อยู่รอด" เพื่อพูด

ตรวจสอบเอกสารเกี่ยวกับทั้งคู่เนื่องจากตรงไปตรงมา ui พิเศษของเราเตอร์ ui ยังมีการสาธิตที่ดีเกี่ยวกับการทำงานของสถานะติดหนึบ


ฉันอ่านเอกสารแล้ว แต่ไม่สามารถหาวิธีรักษาอินพุตแบบฟอร์มเมื่อผู้ใช้คลิกปุ่มย้อนกลับเบราว์เซอร์ คุณช่วยชี้ฉันไปที่เฉพาะเจาะจงได้ไหม ขอบคุณ!
Dalibor

6

ฉันมีปัญหาเดียวกันนี่คือสิ่งที่ฉันทำ: ฉันมีสปาที่มีหลายมุมมองในหน้าเดียวกัน (ไม่มีอาแจ็กซ์) ดังนั้นนี่คือรหัสของโมดูล:

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 เช่น localstorage และอื่น ๆ )


ใช่ทำงานเหมือนมีเสน่ห์ โรงงานข้อมูลของเราได้รับการเข้ารหัสแล้วและฉันใช้ไวยากรณ์ ControllerAs ดังนั้นฉันจึงเขียนมันใหม่เป็นตัวควบคุม $ scope ดังนั้น 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

โซลูชันที่จะทำงานกับหลาย ๆ ขอบเขตและหลายตัวแปรภายในขอบเขตเหล่านั้น

บริการนี้มีพื้นฐานมาจากคำตอบของ Anton แต่สามารถขยายได้มากขึ้นและสามารถทำงานได้ในหลาย ๆ ขอบเขตและอนุญาตให้เลือกตัวแปรขอบเขตหลายตัวในขอบเขตเดียวกัน มันใช้เส้นทางเส้นทางเพื่อจัดทำดัชนีแต่ละขอบเขตจากนั้นชื่อตัวแปรขอบเขตเพื่อทำดัชนีให้ลึกลงหนึ่งระดับ

สร้างบริการด้วยรหัสนี้:

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 เป็นค่าที่เก็บไว้มิฉะนั้นจะใช้ค่าเริ่มต้นเป็นค่าที่ให้และบันทึกไว้ หากหน้าถูกปิดรีเฟรชหรือเปลี่ยนเส้นทางค่าปัจจุบันของตัวแปรขอบเขตที่ลงทะเบียนทั้งหมดจะถูกบันทึกไว้และจะถูกกู้คืนในครั้งต่อไปที่มีการเยี่ยมชมเส้นทาง / หน้า


เมื่อฉันเพิ่มสิ่งนี้ในรหัสของฉันฉันได้รับข้อผิดพลาด "TypeError: ไม่สามารถอ่าน 'locals' ของ undefined" ฉันจะแก้ไขได้อย่างไร @brett
Michael Mahony

คุณใช้ Angular เพื่อจัดการเส้นทางของคุณหรือไม่? ฉันคาดเดาว่า "ไม่" เนื่องจาก $ route.current ดูเหมือนจะไม่ได้กำหนด
Brett Pennings

ใช่มุมกำลังจัดการเส้นทางของฉัน นี่คือตัวอย่างของสิ่งที่ฉันมีในการกำหนดเส้นทาง: JBenchApp.config (['$ routeProvider', '$ locationProvider', ฟังก์ชั่น ($ routeProvider, $ locationProvider) {$ routeProvider เมื่อ {'/ dashboard', {templateUrl: ' partials / dashboard.html ', คอนโทรลเลอร์:' JBenchCtrl '})
Michael Mahony

สิ่งเดียวที่ฉันคาดเดาก็คือตัวควบคุมของคุณกำลังทำงานก่อนที่แอปของคุณจะได้รับการกำหนดค่า ไม่ว่าจะด้วยวิธีใดคุณสามารถแก้ไขปัญหาได้โดยใช้ $ location แทนที่จะเป็น $ route จากนั้นส่งผ่าน $ scope จากคอนโทรลเลอร์แทนที่จะเข้าถึงในเส้นทาง ลองใช้gist.github.com/brettpennings/ae5d34de0961eef010c6สำหรับบริการของคุณแทนและส่งผ่าน $ scope เป็นพารามิเตอร์ที่สามของ restoreScope.GetOrRegisterScopeVariables ในคอนโทรลเลอร์ของคุณ หากสิ่งนี้เหมาะกับคุณฉันอาจทำคำตอบใหม่ของฉัน โชคดี!
Brett Pennings

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>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.