AngularJS'de denetleyiciyi yeniden yüklemeden bir yolu değiştirebilir misiniz?


191

Daha önce sorulmuş ve cevaplardan iyi görünmüyor. Bu örnek kodu dikkate alarak sormak istiyorum ...

Uygulamam, geçerli öğeyi sağlayan hizmete yükler. Öğe yeniden yüklenmeden öğe verilerini işleyen birkaç denetleyici vardır.

Denetleyicilerim henüz ayarlanmadıysa öğeyi yeniden yükler, aksi takdirde hizmette yüklü olan öğeyi denetleyiciler arasında kullanır.

Sorun : Item.html dosyasını yeniden yüklemeden her denetleyici için farklı yollar kullanmak istiyorum.

1) Bu mümkün mü?

2) Bu mümkün değilse, kontrolör başına bir yol bulmak için daha iyi bir yaklaşım var mı?

app.js

var app = angular.module('myModule', []).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/items', {templateUrl: 'partials/items.html',   controller: ItemsCtrl}).
      when('/items/:itemId/foo', {templateUrl: 'partials/item.html', controller: ItemFooCtrl}).
      when('/items/:itemId/bar', {templateUrl: 'partials/item.html', controller: ItemBarCtrl}).
      otherwise({redirectTo: '/items'});
    }]);

Item.html

<!-- Menu -->
<a id="fooTab" my-active-directive="view.name" href="#/item/{{item.id}}/foo">Foo</a>
<a id="barTab" my-active-directive="view.name" href="#/item/{{item.id}}/bar">Bar</a>
<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>

controller.js

// Helper function to load $scope.item if refresh or directly linked
function itemCtrlInit($scope, $routeParams, MyService) {
  $scope.item = MyService.currentItem;
  if (!$scope.item) {
    MyService.currentItem = MyService.get({itemId: $routeParams.itemId});
    $scope.item = MyService.currentItem;
  }
}
function itemFooCtrl($scope, $routeParams, MyService) {
  $scope.view = {name: 'foo', template: 'partials/itemFoo.html'};
  itemCtrlInit($scope, $routeParams, MyService);
}
function itemBarCtrl($scope, $routeParams, MyService) {
  $scope.view = {name: 'bar', template: 'partials/itemBar.html'};
  itemCtrlInit($scope, $routeParams, MyService);
}

Çözüm.

durum : Arama sorgusunu kabul edilen yanıtta önerildiği gibi kullanmak, ana denetleyiciyi yeniden yüklemeden farklı URL'ler sağlamamı sağladı.

app.js

var app = angular.module('myModule', []).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/items', {templateUrl: 'partials/items.html',   controller: ItemsCtrl}).
      when('/item/:itemId/', {templateUrl: 'partials/item.html', controller: ItemCtrl, reloadOnSearch: false}).
      otherwise({redirectTo: '/items'});
    }]);

Item.html

<!-- Menu -->
<dd id="fooTab" item-tab="view.name" ng-click="view = views.foo;"><a href="#/item/{{item.id}}/?view=foo">Foo</a></dd>
<dd id="barTab" item-tab="view.name" ng-click="view = views.bar;"><a href="#/item/{{item.id}}/?view=foo">Bar</a></dd>

<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>

controller.js

function ItemCtrl($scope, $routeParams, Appts) {
  $scope.views = {
    foo: {name: 'foo', template: 'partials/itemFoo.html'},
    bar: {name: 'bar', template: 'partials/itemBar.html'},
  }
  $scope.view = $scope.views[$routeParams.view];
}

directives.js

app.directive('itemTab', function(){
  return function(scope, elem, attrs) {
    scope.$watch(attrs.itemTab, function(val) {
      if (val+'Tab' == attrs.id) {
        elem.addClass('active');
      } else {
        elem.removeClass('active');
      }
    });
  }
});

Kısmi öğelerimin içindeki içerik ng-controller=...


bu yanıtı buldu: stackoverflow.com/a/18551525/632088 - reloadOnSearch: false kullanır, ancak arama çubuğundaki url'deki güncellemeleri de denetler (örneğin, kullanıcı geri düğmesini ve url değişikliklerini tıklarsa)
robert king

Yanıtlar:


115

Eğer gibi URL'leri kullanmak yoksa #/item/{{item.id}}/foove #/item/{{item.id}}/barancak #/item/{{item.id}}/?foove #/item/{{item.id}}/?baryerine ilişkin, rotanızı ayarlayabilirsiniz /item/{{item.id}}/olması reloadOnSearchiçin set false( https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). Bu, AngularJS'ye URL'nin arama kısmı değişirse görünümü yeniden yüklememesini söyler.


Bunun için teşekkürler. Denetleyiciyi nerede değiştireceğimi anlamaya çalışıyorum.
Coder1

Anladım. Benim bakış diziye bir kontrolör param eklendi, daha sonra ilave ng-controller="view.controller"etmek ng-includedirektif.
Coder1

İtemCtrlInit'te $ location.search () üzerinde bir saat oluşturacak ve arama parametresine bağlı olarak $ scope.view.template dosyasını güncelleyeceksiniz. Ve sonra bu şablon benzer bir şeyle sarılabilir <div ng-controller="itemFooCtrl">... your template content...</div>. DÜZENLEME: Çözdüğünüzü görmek güzel, sorunuzu nasıl çözdüğünüzle, belki de bir jsFiddle ile güncellerseniz harika olurdu.
Anders Ekdahl

% 100 orada olduğumda güncelleyeceğim. Çocuk denetleyicileri ve ng-click kapsamı ile bazı tuhaflıklar yaşıyorum. Doğrudan foo'dan yüklersem, ng-click foo kapsamında yürütülür. Daha sonra çubuğa tıklıyorum ve bu şablondaki ng tıklamaları üst kapsamda yürütülür.
Coder1

1
URL'lerinizi gerçekten yeniden biçimlendirmeniz gerekmediğine dikkat edilmelidir. Bu, yanıtın gönderildiği durumda olabilir, ancak belgeler ( docs.angularjs.org/api/ngRoute.$routeProvider ) şu anda şunu söylüyor: [reloadOnSearch = true] - {boolean =} - yalnızca $ konum olduğunda rotayı yeniden yükle. search () veya $ location.hash () değişir.
Rhys van der Waerden

93

Yolu değiştirmeniz gerekirse, bunu uygulama dosyanızdaki .config dosyasından sonra ekleyin. Sonra $location.path('/sampleurl', false);yeniden yüklemeyi önlemek için yapabilirsiniz

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}])

Bulduğum en şık çözüm için kredi https://www.consolelog.io/angularjs-change-path-without-reloading adresine gidiyor .


6
Harika bir çözüm. Tarayıcının geri düğmesini "onurlandırmak" için bir yol var mı?
Mark B

6
Bu çözümün eski rotayı koruduğunu ve $ route.current için sorarsanız, geçerli yolu değil, önceki yolu alacaksınız. Aksi takdirde büyük bir hack.
jornare

4
Sadece orijinal çözümün burada "EvanWinstanley" tarafından acutally gönderildiğini söylemek istedim: github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24 17:14

2
Bu çözümü bir süredir kullanıyorum ve bazı yol değişikliklerinde, gerçek bir değer geçtiğinde bile kayıtları yanlış olarak yeniden yüklediğini fark ettim.
Will Hitchcock

2
$timeout(un, 200);Bazen $locationChangeSuccesssonra hiç ateş vermedi gibi dönüş ifadesi önce eklemek zorunda kaldı apply(ve gerçekten gerekli olduğunda rota değişmedi). Benim çözüm yol çağrıldıktan sonra işleri temizler (..., yanlış)
snowindy

17

neden ng denetleyiciyi bir seviye daha yükseltmiyorsunuz,

<body ng-controller="ProjectController">
    <div ng-view><div>

Ve rotaya kontrolörü ayarlamayın,

.when('/', { templateUrl: "abc.html" })

benim için çalışıyor.


harika bir ipucu, benim senaryom için de mükemmel çalışıyor! çok teşekkür ederim! :)
daveoncode

4
Bu harika bir "
ruslar

1
Çok erken konuştum. Bu geçici çözüm, denetleyiciye $ routeParams öğesinden değerler yüklemeniz gerekirse, varsayılan olarak {}
49'da inolasco

@inolasco: $ routeParams'ınızı $ routeChangeSuccess işleyicisinde okuduğunuzda çalışır. Çoğu zaman, muhtemelen bu zaten istediğiniz şeydir. sadece denetleyicide okumak, yalnızca başlangıçta yüklenen URL'nin değerlerini alır.
plong0

@ plong0 İyi bir nokta, işe yaramalı. Benim durumumda olsa da, URL değerlerini yalnızca denetleyici sayfa yüklendiğinde yüklendiğinde, belirli değerleri başlatmak için gerekliydi. Vigrond tarafından $ locationChangeSuccess kullanarak önerilen çözümü kullandım, ancak sizinki de geçerli bir başka seçenek.
inolasco


10

Bu gönderi eski olsa ve bir yanıtı kabul etmiş olsa da, reloadOnSeach = false kullanmak, sadece paramitleri değil, gerçek yolu değiştirmesi gerekenlerimiz için sorunu çözmez. İşte dikkate almanız gereken basit bir çözüm:

Ng-view yerine ng-include kullanın ve kontrol cihazınızı şablona atayın.

<!-- In your index.html - instead of using ng-view -->
<div ng-include="templateUrl"></div>

<!-- In your template specified by app.config -->
<div ng-controller="MyController">{{variableInMyController}}</div>

//in config
$routeProvider
  .when('/my/page/route/:id', { 
    templateUrl: 'myPage.html', 
  })

//in top level controller with $route injected
$scope.templateUrl = ''

$scope.$on('$routeChangeSuccess',function(){
  $scope.templateUrl = $route.current.templateUrl;
})

//in controller that doesn't reload
$scope.$on('$routeChangeSuccess',function(){
  //update your scope based on new $routeParams
})

Yalnızca aşağı tarafı, çözünürlük özniteliğini kullanamamanızdır, ancak bu, etrafta dolaşmak için oldukça kolaydır. Ayrıca, ilgili url değiştikçe yol denetleyici içinde değiştikçe $ routeParams'a dayalı mantık gibi denetleyicinin durumunu da yönetmeniz gerekir.

İşte bir örnek: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview


1
Geri düğmesinin plnkr'de doğru davranacağından emin değilim ... Bahsettiğim gibi, rota değiştikçe bazı şeyleri kendi başınıza yönetmeniz gerekiyor .. Bu en koda layık bir çözüm değil, ama işe yarıyor
Lukus

2

Bu çözümü kullanıyorum

angular.module('reload-service.module', [])
.factory('reloadService', function($route,$timeout, $location) {
  return {
     preventReload: function($scope, navigateCallback) {
        var lastRoute = $route.current;

        $scope.$on('$locationChangeSuccess', function() {
           if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
              var routeParams = angular.copy($route.current.params);
              $route.current = lastRoute;
              navigateCallback(routeParams);
           }
        });
     }
  };
})

//usage
.controller('noReloadController', function($scope, $routeParams, reloadService) {
     $scope.routeParams = $routeParams;

     reloadService.preventReload($scope, function(newParams) {
        $scope.routeParams = newParams;
     });
});

Bu yaklaşım, düğme işlevini korur ve gördüğüm diğer yaklaşımların aksine, şablonda her zaman geçerli routeParams'a sahip olursunuz.


1

GitHub dahil olmak üzere yukarıdaki cevapların senaryom için bazı sorunları vardı ve ayrıca tarayıcıdan geri düğmesi veya doğrudan url değişikliği, sevmediğim denetleyiciyi yeniden yüklüyordu. Sonunda şu yaklaşımla gittim:

1. Denetleyicinin rota değişikliğinde yeniden yüklenmesini istemediğiniz yollar için 'noReload' adlı rota tanımlarında bir özellik tanımlayın.

.when('/:param1/:param2?/:param3?', {
    templateUrl: 'home.html',
    controller: 'HomeController',
    controllerAs: 'vm',
    noReload: true
})

2. Modülünüzün çalışma işlevinde, bu yolları kontrol eden mantığı koyun. Sadece eğer yeniden yüklenmesini önleyecek noReloadolan trueve önceki rota kontrolör aynıdır.

fooRun.$inject = ['$rootScope', '$route', '$routeParams'];

function fooRun($rootScope, $route, $routeParams) {
    $rootScope.$on('$routeChangeStart', function (event, nextRoute, lastRoute) {
        if (lastRoute && nextRoute.noReload 
         && lastRoute.controller === nextRoute.controller) {
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                un();
                // Broadcast routeUpdate if params changed. Also update
                // $routeParams accordingly
                if (!angular.equals($route.current.params, lastRoute.params)) {
                    lastRoute.params = nextRoute.params;
                    angular.copy(lastRoute.params, $routeParams);
                    $rootScope.$broadcast('$routeUpdate', lastRoute);
                }
                // Prevent reload of controller by setting current
                // route to the previous one.
                $route.current = lastRoute;
            });
        }
    });
}

3. Son olarak, denetleyicide $ routeUpdate olayını dinleyin, böylece rota parametreleri değiştiğinde yapmanız gereken her şeyi yapabilirsiniz.

HomeController.$inject = ['$scope', '$routeParams'];

function HomeController($scope, $routeParams) {
    //(...)

    $scope.$on("$routeUpdate", function handler(route) {
        // Do whatever you need to do with new $routeParams
        // You can also access the route from the parameter passed
        // to the event
    });

    //(...)
}

Bu yaklaşımla, denetleyicideki şeyleri değiştirmeyeceğinizi ve ardından yolu buna göre güncelleyeceğinizi unutmayın. Öteki yol bu. Önce yolu değiştirin, sonra rota parametreleri değiştiğinde denetleyicideki şeyleri değiştirmek için $ routeUpdate olayını dinleyin.

Bu, hem yolu değiştirdiğinizde (ancak isterseniz pahalı $ http istekleri olmadan) hem de tarayıcıyı tamamen yeniden yüklediğinizde aynı mantığı kullanabileceğiniz için işleri basit ve tutarlı tutar.


1

Yeniden yüklemeden yolu değiştirmenin basit bir yolu var

URL is - http://localhost:9000/#/edit_draft_inbox/1457

URL'yi değiştirmek için bu kodu kullanın, Sayfa yönlendirilmeyecek

İkinci parametre "false" çok önemlidir.

$location.path('/edit_draft_inbox/'+id, false);

Bu angularjs için doğru değil docs.angularjs.org/api/ng/service/$location
steampowered

0

@Vigrond ve @rahilwazir'in kaçırdığı birkaç şeyi çözen tam çözümüm:

  • Arama parametreleri değiştirildiği zaman, a $routeUpdate.
  • Rota gerçekten değişmeden bırakıldığında $locationChangeSuccess, bir sonraki rota güncellemesinin önlenmesine neden olan hiçbir zaman tetiklenmez .
  • Aynı özet döngüsünde, bu kez yeniden yüklemek isteyen başka bir güncelleme isteği varsa, olay işleyicisi bu yeniden yüklemeyi iptal eder.

    app.run(['$rootScope', '$route', '$location', '$timeout', function ($rootScope, $route, $location, $timeout) {
        ['url', 'path'].forEach(function (method) {
            var original = $location[method];
            var requestId = 0;
            $location[method] = function (param, reload) {
                // getter
                if (!param) return original.call($location);
    
                # only last call allowed to do things in one digest cycle
                var currentRequestId = ++requestId;
                if (reload === false) {
                    var lastRoute = $route.current;
                    // intercept ONLY the next $locateChangeSuccess
                    var un = $rootScope.$on('$locationChangeSuccess', function () {
                        un();
                        if (requestId !== currentRequestId) return;
    
                        if (!angular.equals($route.current.params, lastRoute.params)) {
                            // this should always be broadcast when params change
                            $rootScope.$broadcast('$routeUpdate');
                        }
                        var current = $route.current;
                        $route.current = lastRoute;
                        // make a route change to the previous route work
                        $timeout(function() {
                            if (requestId !== currentRequestId) return;
                            $route.current = current;
                        });
                    });
                    // if it didn't fire for some reason, don't intercept the next one
                    $timeout(un);
                }
                return original.call($location, param);
            };
        });
    }]);

pathSonunda yazım hatası olmalı param, ayrıca bu karmalarla çalışmıyor ve adres
çubuğumdaki

Sabit. Hmm garip html5 URL'lerinde olsa benim için çalışıyor. Tahmin edebileceğim birine hala yardımcı olabilir ...
Oded Niv

0

Aşağıdaki etiketin içine ekle

  <script type="text/javascript">
    angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));
  </script>

Bu yeniden yüklemeyi önleyecektir.


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.