Pertahankan model cakupan saat mengubah antar tampilan di AngularJS

152

Saya belajar AngularJS. Katakanlah saya memiliki / view1 menggunakan My1Ctrl , dan / view2 menggunakan My2Ctrl ; yang dapat dinavigasi untuk menggunakan tab di mana setiap tampilan memiliki bentuknya sendiri yang sederhana namun berbeda.

Bagaimana saya memastikan bahwa nilai yang dimasukkan dalam bentuk view1 tidak diatur ulang, ketika pengguna meninggalkan dan kemudian kembali ke view1 ?

Apa yang saya maksud adalah, bagaimana kunjungan kedua ke view1 dapat mempertahankan model yang sama persis seperti ketika saya meninggalkannya?

JeremyWeir
sumber

Jawaban:

174

Saya mengambil sedikit waktu untuk mencari tahu apa cara terbaik untuk melakukan ini. Saya juga ingin mempertahankan status, ketika pengguna meninggalkan halaman dan kemudian menekan tombol kembali, untuk kembali ke halaman lama; dan tidak hanya memasukkan semua data saya ke rootscope .

Hasil akhirnya adalah memiliki layanan untuk setiap pengontrol . Di controller, Anda hanya memiliki fungsi dan variabel yang tidak Anda pedulikan, jika mereka dihapus.

Layanan untuk pengontrol disuntikkan dengan injeksi ketergantungan. Karena layanan adalah lajang, data mereka tidak dimusnahkan seperti data di controller.

Dalam layanan, saya punya model. model HANYA memiliki data - tidak ada fungsi -. Dengan begitu bisa dikonversi bolak-balik dari JSON untuk bertahan. Saya menggunakan penyimpanan lokal HTML5 untuk kegigihan.

Terakhir saya menggunakan window.onbeforeunloaddan$rootScope.$broadcast('saveState'); memberi tahu semua layanan bahwa mereka harus menyelamatkan negara mereka, dan $rootScope.$broadcast('restoreState')membiarkan mereka tahu untuk memulihkan keadaan mereka (digunakan ketika pengguna meninggalkan halaman dan menekan tombol kembali untuk kembali ke halaman masing-masing).

Contoh layanan bernama userService untuk userController saya :

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

contoh userController

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

Tampilan kemudian menggunakan penjilidan seperti ini

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

Dan di modul aplikasi , dalam menjalankan fungsi saya menangani penyiaran dari saveState dan 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');
};

Seperti yang saya sebutkan ini butuh beberapa saat untuk sampai ke titik ini. Ini adalah cara yang sangat bersih untuk melakukannya, tetapi itu adalah sedikit rekayasa untuk melakukan sesuatu yang saya duga adalah masalah yang sangat umum ketika berkembang di Angular.

Saya ingin melihat lebih mudah, tetapi sebagai cara bersih untuk menangani menjaga status melintasi pengontrol, termasuk ketika pengguna pergi dan kembali ke halaman.

Anton
sumber
10
Ini menjelaskan cara mempertahankan data saat halaman diperbarui, bukan bagaimana bertahan saat rute berubah, sehingga tidak menjawab pertanyaan. Plus itu tidak akan berfungsi untuk aplikasi yang tidak menggunakan rute. Plus itu tidak menunjukkan ke mana sessionStorage.restoreStatediatur "true". Untuk solusi yang benar untuk mempertahankan data saat halaman disegarkan, lihat di sini . Untuk data yang ada saat rute berubah, lihat jawaban carloscarcamo. Yang Anda butuhkan hanyalah memasukkan data ke dalam layanan.
Hugo Wood
2
itu tetap ada di seluruh tampilan, dengan cara yang persis sama seperti yang Anda sarankan, simpan dalam layanan. cukup menarik, itu juga bertahan pada halaman mengubah cara yang sama Anda saran dengan window.onbeforeunload. terima kasih telah membuat saya memeriksa ulang. jawaban ini sudah berumur lebih dari satu tahun dan sepertinya masih cara paling sederhana untuk melakukan sesuatu dengan sudut.
Anton
Terima kasih telah datang kembali ke sini, meskipun sudah lama :). Tentu solusi Anda mencakup kedua tampilan dan halaman, tetapi tidak menjelaskan kode mana yang melayani tujuan apa, dan itu tidak cukup lengkap sehingga dapat digunakan . Saya hanya memperingatkan pembaca untuk menghindari pertanyaan lebih lanjut . Alangkah baiknya jika Anda punya waktu untuk mengedit untuk meningkatkan jawaban Anda.
Hugo Wood
Bagaimana jika di controller Anda bukan ini: $scope.user = userService;Anda memiliki sesuatu seperti ini: $scope.name = userService.model.name; $scope.email = userService.model.email;. Apakah ini akan berfungsi atau mengubah variabel dalam pandangan tidak akan mengubahnya dalam Layanan?
Olahraga
2
Saya pikir sudut tidak memiliki mekanisme
bawaan untuk
22

Agak terlambat untuk jawaban tetapi baru saja memperbarui biola dengan beberapa praktik terbaik

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;
    }
}
Atul Chaudhary
sumber
Saya bahkan harus melakukan ini di antara navigasi halaman (bukan refresh halaman). Apakah ini benar?
FutuToad
Saya pikir saya harus menambahkan bagian untuk menyelesaikan jawaban ini, yang updateServiceharus dipanggil ketika controller dihancurkan - $scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
Shivendra
10

$ rootScope adalah variabel global besar, yang baik untuk hal-hal satu kali, atau aplikasi kecil. Gunakan layanan jika Anda ingin merangkum model dan / atau perilaku Anda (dan mungkin menggunakannya kembali di tempat lain). Selain posting grup OP Google yang disebutkan, lihat juga https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion .

Mark Rajcok
sumber
8

Sudut tidak benar-benar memberikan apa yang Anda cari di luar kotak. Apa yang akan saya lakukan untuk mencapai apa yang Anda cari adalah menggunakan add-ons berikut

Router UI & UI Router Ekstra

Kedua hal ini akan memberi Anda perutean berbasis negara dan status lengket, Anda dapat menabrak antara status dan semua informasi akan disimpan sebagai ruang lingkup "tetap hidup" untuk berbicara.

Periksa dokumentasi pada keduanya karena cukup lurus ke depan, ui router tambahan juga memiliki demonstrasi yang bagus tentang cara kerja sticky state.

Joshua Kelly
sumber
Saya sudah membaca dokumen, tetapi tidak bisa menemukan cara menyimpan input formulir ketika pengguna mengklik tombol kembali browser. Bisakah Anda mengarahkan saya ke spesifik, tolong? Terima kasih!
Dalibor
6

Saya punya masalah yang sama, Ini yang saya lakukan: Saya punya SPA dengan banyak tampilan di halaman yang sama (tanpa ajax), jadi ini adalah kode modul:

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

Saya hanya memiliki satu pengontrol untuk semua tampilan, tetapi, masalahnya sama dengan pertanyaan, pengontrol selalu menyegarkan data, untuk menghindari perilaku ini, saya melakukan apa yang orang sarankan di atas dan saya membuat layanan untuk tujuan itu, kemudian meneruskannya ke controller sebagai berikut:

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

Sekarang saya dapat memanggil fungsi pembaruan dari semua pandangan saya, memberikan nilai dan memperbarui model saya, saya tidak perlu menggunakan html5 apis untuk data ketekunan (ini dalam kasus saya, mungkin dalam kasus lain mungkin perlu menggunakan html5 apis seperti penyimpanan lokal dan barang lainnya).

carloscarcamo
sumber
ya, bekerja seperti pesona. Pabrik data kami sudah dikodekan dan saya menggunakan sintaks ControllerAs, jadi saya telah menulis ulang ke $ scope controller, sehingga Angular dapat mengendalikannya. Implementasinya sangat mudah. Ini adalah aplikasi sudut pertama yang saya bungkus. tks
egidiocs
2

Alternatif untuk layanan adalah menggunakan value store.

Di dasar aplikasi saya, saya menambahkan ini

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

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

Dan kemudian di controller saya, saya hanya referensi toko nilai. Saya tidak berpikir itu berlaku jika pengguna menutup browser.

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
sumber
1

Solusi yang akan bekerja untuk beberapa cakupan dan beberapa variabel dalam lingkup tersebut

Layanan ini didasarkan pada jawaban Anton, tetapi lebih dapat diperluas dan akan bekerja lintas beberapa lingkup dan memungkinkan pemilihan beberapa variabel lingkup dalam lingkup yang sama. Ini menggunakan jalur rute untuk mengindeks setiap lingkup, dan kemudian nama variabel lingkup untuk mengindeks satu tingkat lebih dalam.

Buat layanan dengan kode ini:

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

Tambahkan kode ini ke fungsi jalankan Anda di modul aplikasi Anda:

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

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

Suntikkan layanan restoreScope ke pengontrol Anda (contoh di bawah):

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

Contoh di atas akan menginisialisasi $ scope.user ke nilai yang disimpan, jika tidak maka akan default ke nilai yang disediakan dan menyimpannya. Jika halaman ditutup, disegarkan, atau rute diubah, nilai saat ini dari semua variabel lingkup terdaftar akan disimpan, dan akan dipulihkan pada saat rute / halaman dikunjungi berikutnya.

Brett Pennings
sumber
Ketika saya menambahkan ini ke kode saya, saya mendapatkan kesalahan "TypeError: Tidak dapat membaca properti 'lokal' dari undefined" Bagaimana cara memperbaikinya? @brett
Michael Mahony
Apakah Anda menggunakan Angular untuk menangani rute Anda? Saya menduga "tidak" karena $ route.current tampaknya tidak terdefinisi.
Brett Pennings
Ya, angular menangani perutean saya. Berikut adalah contoh dari apa yang saya miliki dalam perutean: JBenchApp.config (['$ routeProvider', '$ locationProvider', fungsi ($ routeProvider, $ locationProvider) {$ routeProvider. {$ / Route dashboard. When ('/ dashboard', {templateUrl: ' partials / dashboard.html ', controller:' JBenchCtrl '})
Michael Mahony
Satu-satunya tebakan saya lainnya adalah bahwa pengontrol Anda berjalan sebelum aplikasi Anda dikonfigurasi. Either way, Anda mungkin bisa mengatasi masalah ini dengan menggunakan $ location bukannya $ route dan kemudian melewati $ scope dari controller daripada mengaksesnya di rute. Coba gunakan gist.github.com/brettpennings/ae5d34de0961eef010c6 sebagai gantinya dan berikan $ scope sebagai parameter ketiga dari restoreScope.GetOrRegisterScopeVariables di controller Anda. Jika ini bekerja untuk Anda, saya mungkin hanya membuat jawaban baru saya. Semoga berhasil!
Brett Pennings
1

Anda dapat menggunakan $locationChangeStartacara untuk menyimpan nilai sebelumnya di $rootScopeatau dalam layanan. Ketika Anda kembali, cukup inisialisasi semua nilai yang disimpan sebelumnya. Berikut ini adalah demo cepat yang digunakan $rootScope.

masukkan deskripsi gambar di sini

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>

Hari Das
sumber