AngularJS'de görünümler arasında geçiş yaparken kapsam modelini koru


152

AngularJS öğreniyorum. Diyelim ki My1Ctrl kullanarak / view1 ve My2Ctrl kullanarak / view2 ; her bir görünümün kendi basit ancak farklı bir formunun bulunduğu sekmeler kullanılarak yönlendirilebilir.

Nasıl yapacak emin şeklinde girilen değerler olduğunu view1 sıfırlanmaz, bir kullanıcı yaprakları ve sonra döner view1 ?

Demek istediğim, view1'e yapılan ikinci ziyaret , modelin bıraktığım haliyle tam olarak aynı durumu nasıl koruyabilir ?

Yanıtlar:


174

Bunu yapmanın en iyi yolunu bulmak için biraz zaman ayırdım. Ayrıca, kullanıcı sayfadan ayrıldıktan sonra geri düğmesine bastığında, eski sayfaya geri dönmek için durumu korumak istedim; ve tüm verilerimi anaç içine koymakla kalmadı .

Nihai sonuç, her denetleyici için bir hizmete sahip olmaktır . Denetleyicide, temizlenmişse, umursamadığınız işlevler ve değişkenler vardır.

Kontrolör servisi bağımlılık enjeksiyonu ile enjekte edilir. Hizmetler tekil olduğundan, denetleyicideki veriler gibi verileri yok edilmez.

Hizmette bir modelim var. SADECE model veri içerir - işlev yok -. Bu şekilde, devam etmek için JSON'dan ileri geri dönüştürülebilir. Kalıcılık için html5 localstorage kullandım.

Son olarak kullandım window.onbeforeunloadve $rootScope.$broadcast('saveState');tüm hizmetlerin durumlarını kaydetmeleri gerektiğini $rootScope.$broadcast('restoreState')bilmesini ve durumlarını geri yüklemelerini bilmelerini sağlamak için (kullanıcı sayfadan ayrıldığında ve sayfaya geri dönmek için geri düğmesine bastığında kullanılır).

Denilen Örnek servisi userService benim için 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 örneği

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

Görünüm daha sonra böyle bir bağlama kullanır

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

Ve de uygulama modülü , çalışma fonksiyonu içinde i idare yayın arasında Savestate ve 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');
};

Dediğim gibi bu noktaya gelmek biraz zaman aldı. Bunu yapmanın çok temiz bir yoludur, ancak Angular'da gelişirken çok yaygın bir sorun olduğundan şüphelendiğim bir şey yapmak oldukça makul bir mühendisliktir.

Daha kolay görmek isterim, ancak kullanıcının sayfadan ayrılıp sayfaya geri dönmesi de dahil olmak üzere durumu denetleyiciler arasında tutmanın temiz yolları olarak görmek isterim.


10
Bu, sayfa yenilendiğinde verilerin nasıl saklanacağını açıklar, rota değiştiğinde nasıl saklanacağını değil, soruyu cevaplamaz. Ayrıca rota kullanmayan uygulamalar için çalışmaz. Ayrıca, nerede sessionStorage.restoreStateolarak ayarlandığını göstermez "true". Sayfa yenilendiğinde verilerin saklanmasına yönelik doğru bir çözüm için buraya bakın . Rota değiştiğinde kalıcı veriler için carloscarcamo'nun cevabına bakın. Tek ihtiyacınız olan verileri bir hizmete sokmak.
Hugo Wood

2
görünümler boyunca, önerdiğiniz gibi, bir hizmette saklar. ilginç bir şekilde, window.onbeforeunload ile önerdiğiniz şekilde bir sayfa değişikliğinde de devam eder. Bana çift kontrol yaptığınız için teşekkürler. bu cevap bir yıldan daha eski ve hala köşeli işleri yapmanın en basit yolu gibi görünüyor.
Anton

Eski olmasına rağmen buraya geldiğiniz için teşekkür ederim :). Çözümünüzün hem görünümleri hem de sayfaları kapsadığından emin olun, ancak hangi kodun hangi amaca hizmet ettiğini açıklamaz ve kullanılabilir olabileceği kadar tam değildir . Başka soruları önlemek için sadece okuyucuları uyarıyordum . Cevabınızı geliştirmek için düzenlemeler yapmak için zamanınız olsaydı harika olurdu.
Hugo Wood

Bunun yerine sizin denetleyicisi ne olursa: $scope.user = userService;Eğer böyle bir şey var: $scope.name = userService.model.name; $scope.email = userService.model.email;. Çalışır mı yoksa görünümdeki değişkenleri değiştirirse Hizmet'te değişmez mi?
spor

2
bence açısal böyle ortak bir şey için yerleşik bir mekanizmaya sahip değil
obe6

22

Bir cevap için biraz geç ama bazı en iyi uygulamalarla güncellenmiş keman

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

Bunu sayfa gezintileri arasında bile yapmak zorundayım (sayfa yenilemeleri değil). bu doğru mu?
FutuToad

Ben bu cevabı tamamlamak için bir parça eklemeniz gerektiğini düşünüyorum updateService, denetleyici yok edildiğinde $scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
çağrılmalıdır


8

Angular aradığınızı gerçekten kutudan çıkarmaz. Ne sonra yapmak için ne yapardı aşağıdaki eklentileri kullanmak

UI Router ve UI Router Ekstraları

Bu ikisi size eyalet tabanlı yönlendirme ve yapışkan durumlar sağlar, durumlar arasında sekme yapabilirsiniz ve tüm bilgiler kapsam "canlı kalır" olarak kaydedilecektir.

Her ikisi de oldukça düz olduğundan, ui yönlendirici ekstralarının da yapışkan durumların nasıl çalıştığına dair iyi bir gösterimi vardır .


Belgeleri okudum, ancak kullanıcı tarayıcı geri düğmesini tıkladığında form girişlerinin nasıl korunacağını bulamadım. Beni detaylara işaret edebilir misiniz, lütfen? Teşekkürler!
Dalibor

6

Ben de aynı sorun vardı, Ben yaptım: Ben aynı sayfada (ajax olmadan) birden fazla görünümleri olan bir SPA var, bu yüzden bu modülün kodu:

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üm görünümler için sadece bir denetleyicim var, ancak sorun soru ile aynı, denetleyici her zaman verileri yeniliyor, bu davranışı önlemek için insanların yukarıda önerdiğini yaptım ve bu amaçla bir hizmet oluşturdum, sonra geçtim denetleyiciye aşağıdaki gibi:

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

Şimdi görünümlerimin herhangi birinden güncelleme işlevini çağırabilir, değerleri geçebilir ve modelimi güncelleyebilirim, kalıcılık verileri için html5 apis kullanmaya gerek yok (bu benim durumumda, belki diğer durumlarda html5 kullanmak gerekli olacaktır) apis gibi localstorage ve diğer şeyler).


evet, bir cazibe gibi çalıştı. Veri fabrikamız zaten kodlanmıştı ve ControllerAs sözdizimini kullanıyordum, bu yüzden bunu $ scope controller'a yeniden yazdım, böylece Angular bunu kontrol edebilirdi. Uygulama çok basitti. Bu ilk açısal uygulama ben writtng olduğunu. tks
egidiocs

2

Hizmetlere bir alternatif de değer deposunu kullanmaktır.

Uygulamamın temelinde bunu ekledim

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

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

Ve sonra kontrol cihazımda sadece değer deposuna başvuruyorum. Kullanıcı tarayıcıyı kapatırsa bunun bir şey tuttuğunu sanmıyorum.

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

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

1

Bu kapsamlardaki birden çok kapsam ve birden çok değişken için çalışacak çözüm

Bu hizmet Anton'un cevabına dayanıyordu, ancak daha genişletilebilir ve birden çok kapsamda çalışacak ve aynı kapsamda birden çok kapsam değişkeninin seçilmesine izin verecektir. Her kapsamı dizine eklemek için yol yolunu ve bir düzey daha derine dizin oluşturmak için kapsam değişkeni adlarını kullanır.

Bu kodla hizmet oluşturun:

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

Bu kodu uygulama modülünüzdeki çalıştırma işlevinize ekleyin:

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

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

RestoreScope hizmetini kumandanıza enjekte edin (aşağıdaki örnek):

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

Yukarıdaki örnek $ scope.user öğesini saklanan değere başlatır, aksi takdirde varsayılan olarak sağlanan değere döndürür ve bunu kaydeder. Sayfa kapatılırsa, yenilenirse veya rota değiştirilirse, kayıtlı tüm kapsam değişkenlerinin geçerli değerleri kaydedilir ve rota / sayfa sonraki ziyaretinde geri yüklenir.


Bunu koduma eklediğimde bir hata alıyorum "TypeError: undefined" locals "özelliği okunamıyor" Bunu nasıl düzeltebilirim? @brett
Michael Mahony

Rotanızı idare etmek için Angular kullanıyor musunuz? $ Route.current undefined gibi görünüyor çünkü "hayır" tahmin ediyorum.
Brett Pennings

Evet, açısal benim rotamı idare ediyor. Yönlendirmedeki örneklere bir örnek: JBenchApp.config (['$ routeProvider', '$ locationProvider', işlev ($ routeProvider, $ locationProvider) {$ routeProvider. When ('/ dashboard', {templateUrl: ' partials / dashboard.html ', denetleyici:' JBenchCtrl '})
Michael Mahony

Diğer tek tahminim, uygulamanız yapılandırılmadan önce denetleyicinizin çalışıyor olmasıdır. Her iki durumda da, $ route yerine $ location kullanarak ve sonra rotadan erişmek yerine denetleyiciden $ scope ileterek sorunu çözebilirsiniz. Bunun yerine hizmetiniz için gist.github.com/brettpennings/ae5d34de0961eef010c6 kullanmayı deneyin ve denetleyicinizdeki üçüncü restoreScope.GetOrRegisterScopeVariables parametreniz olarak $ kapsamını aktarın. Bu sizin için işe yararsa, yeni cevabımı verebilirim. İyi şanslar!
Brett Pennings

1

$locationChangeStartÖnceki değeri $rootScopebir hizmette veya hizmette saklamak için olayı kullanabilirsiniz . Geri döndüğünüzde, önceden kaydedilmiş tüm değerleri başlatmanız yeterlidir. İşte kullanarak hızlı bir demo $rootScope.

resim açıklamasını buraya girin

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>

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.