AngularJSでビュー間を変更するときにスコープのモデルを維持します


152

私はAngularJSを学んでいます。のは、私が持っているとしましょう/ VIEW1を使用してMy1Ctrlを、および/ VIEW2使用My2Ctrlを。これは、各ビューに独自のシンプルだが異なるフォームがあるタブを使用してナビゲートできます。

ユーザーが去ってからview1に戻ったときに、view1の形式で入力した値がリセットされないようにするにはどうすればよいですか?

つまりview1への2回目の訪問で、モデルを去ったときとまったく同じ状態を維持するにはどうすればよいのでしょうか。

回答:


174

私はこれを行うための最良の方法を見つけるのに少し時間をかけました。また、ユーザーがページを離れて[戻る]ボタンを押したときに、元のページに戻るために、状態を維持したいと思っていました。すべてのデータをルートスコープに入れるだけではありません。

最終結果は、各コントローラーにサービスを提供することです。コントローラーには、気にしない関数と変数があり、それらがクリアされている場合のみです。

コントローラーのサービスは、依存関係の注入によって注入されます。サービスはシングルトンであるため、それらのデータはコントローラー内のデータのように破棄されません。

サービスでは、モデルがあります。モデルにはデータのみがあり、関数はありません。そうすれば、JSONとの間で相互に変換して永続化できます。永続化のためにhtml5 localstorageを使用しました。

最後に私が使用window.onbeforeunloadして$rootScope.$broadcast('saveState');、すべてのサービスは、彼らが自分の状態を保存しなければならない、とすることを知っているように$rootScope.$broadcast('restoreState')、彼らが(それぞれのページに戻るには、ときに、ユーザーの葉ページやプレス戻るボタンを使用)その状態を復元するために知っているように。

私のuserControllerのuserServiceというサービスの例:

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>

そして、中にアプリモジュール、実行機能の中に私が扱う放送ステートセーブ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
それはあなたが提案するのと全く同じ方法で、ビューを越えて持続し、サービスに保存します。おもしろいことに、window.onbeforeunloadで提案したのと同じ方法で、ページの変更時にも持続します。ダブルチェックしてくれてありがとう。この回答は1年以上前のものであり、今でも、angularを使用する最も簡単な方法のようです。
アントン

古くてもここに戻ってくれてありがとう:) ソリューションがビューとページの両方をカバーしていることは確かですが、どのコードがどのような目的を果たしているかは説明されておらず、使用できるほど完全ではありません。私は読者にさらなる質問を避けるよう警告するだけでした。回答を改善するために編集する時間があれば、すばらしいでしょう。
Hugo Wood

何これに代えて、あなたのコントローラである場合:$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 14

この回答を完了するには、この部分を追加する必要があると思います。updateServiceコントローラーが破壊されたときに呼び出される必要があります $scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
Shivendra


8

Angularは、あなたが探しているものをそのまま提供するわけではありません。あなたが何をしているかを達成するために私がすることは、次のアドオンを使用することです

UIルーターUIルーターエクストラ

これら2つは、状態ベースのルーティングとスティッキー状態を提供します。状態間でタブを付けることができ、すべての情報は、いわば「生存し続ける」スコープとして保存されます。

どちらも非常にわかりやすいので、両方のドキュメントを確認してください。UIルーターエクストラには、スティッキーステートがどのように機能するかを示す優れたデモもあります。


ドキュメントを読みましたが、ユーザーがブラウザーの戻るボタンをクリックしたときにフォーム入力を保持する方法が見つかりませんでした。詳細を教えていただけますか?ありがとう!
Dalibor

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

私はすべてのビューに1つのコントローラーしかありませんが、問題は質問と同じです。コントローラーは常にデータを更新します。この動作を回避するために、私は上記の人々が提案したことを行い、その目的のためにサービスを作成してから渡しました次のようにコントローラに:

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

これで、ビューのいずれかからupdate関数を呼び出し、値を渡してモデルを更新できます。永続データにhtml5 APIを使用する必要はありません(これは私の場合ですが、他の場合にはhtml5を使用する必要があります) localstorageやその他のもののようなAPI)。


はい、魅力のように働きました。私たちのデータファクトリは既にコード化されており、ControllerAs構文を使用していたため、Angularが制御できるように$ scope controllerに書き直しました。実装は非常に簡単でした。これは私が書いた最初の角アプリです。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の回答に基づいていましたが、拡張性が高く、複数のスコープにわたって機能し、同じスコープ内の複数のスコープ変数を選択できます。ルートパスを使用して各スコープにインデックスを付け、次にスコープ変数名を使用して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を初期化します。それ以外の場合は、デフォルトで提供された値に設定し、それを保存します。ページが閉じられたり、更新されたり、ルートが変更されたりすると、登録されているすべてのスコープ変数の現在の値が保存され、次にルート/ページにアクセスしたときに復元されます。


これをコードに追加すると、「TypeError:プロパティ 'locals' of undefined」というエラーが表示されます。どうすれば修正できますか?@ブレット
マイケルマホニー

Angularを使用してルートを処理していますか?$ route.currentが未定義のように見えるので、私は「いいえ」と推測しています。
ブレットペニングス

はい、angularが私のルーティングを処理しています。これが私がルーティングに持っているもののサンプルです:JBenchApp.config(['$ routeProvider'、 '$ locationProvider'、function($ routeProvider、$ locationProvider){$ routeProvider。when( '/ dashboard'、{templateUrl: ' partials / dashboard.html '、controller:' JBenchCtrl '})
Michael Mahony

私の他の唯一の推測は、アプリが設定される前にコントローラーが実行されていることです。どちらの方法でも、$ routeの代わりに$ locationを使用して、ルートでアクセスする代わりにコントローラーから$ scopeを渡すことで、問題を回避できるでしょう。代わりにサービスにgist.github.com/brettpennings/ae5d34de0961eef010c6を使用してみて、コントローラーのrestoreScope.GetOrRegisterScopeVariablesの3番目のパラメーターとして$ scopeを渡してください。これがあなたのために働くならば、私はそれを私の新しい答えにするかもしれません。幸運を!
ブレットペニングス

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.