Bisakah Anda mengubah jalur tanpa memuat ulang pengontrol di AngularJS?

191

Sudah ditanyakan sebelumnya, dan dari jawaban itu tidak terlihat bagus. Saya ingin bertanya dengan kode sampel ini sebagai pertimbangan ...

Aplikasi saya memuat item saat ini di layanan yang menyediakannya. Ada beberapa pengontrol yang memanipulasi data item tanpa item dimuat ulang.

Pengontrol saya akan memuat ulang item jika belum disetel, jika tidak, ia akan menggunakan item yang saat ini dimuat dari layanan, antara pengontrol.

Masalah : Saya ingin menggunakan jalur berbeda untuk setiap pengontrol tanpa memuat ulang Item.html.

1) Apakah itu mungkin?

2) Jika itu tidak mungkin, apakah ada pendekatan yang lebih baik untuk memiliki path per controller vs apa yang saya temukan di sini?

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

Resolusi.

Status : Menggunakan kueri penelusuran seperti yang disarankan dalam jawaban yang diterima memungkinkan saya memberikan url yang berbeda tanpa memuat ulang pengontrol utama.

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

Konten di dalam parsial saya dibungkus dengan ng-controller=...

Coder1
sumber
menemukan jawaban ini: stackoverflow.com/a/18551525/632088 - ia menggunakan reloadOnSearch: false, tetapi juga memeriksa pembaruan ke url di bilah pencarian (mis. jika pengguna mengklik tombol kembali & perubahan url)
robert king

Jawaban:

115

Jika Anda tidak harus menggunakan URL seperti #/item/{{item.id}}/foodan #/item/{{item.id}}/bartapi #/item/{{item.id}}/?foodan #/item/{{item.id}}/?barsebagai gantinya, Anda dapat mengatur rute Anda untuk /item/{{item.id}}/memiliki reloadOnSearchset ke false( https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). Itu memberitahu AngularJS untuk tidak memuat ulang tampilan jika bagian pencarian dari url berubah.

Anders Ekdahl
sumber
Terima kasih untuk itu. Saya mencoba mencari tahu di mana saya kemudian akan mengubah controller.
Coder1
Mengerti. Menambahkan param controller ke view array saya, kemudian ditambahkan ng-controller="view.controller"ke ng-includedirektif.
Coder1
Anda akan membuat arloji di $ location.search () di itemCtrlInit dan tergantung pada parameter pencarian, perbarui $ scope.view.template. Dan kemudian template itu bisa dibungkus dengan sesuatu seperti <div ng-controller="itemFooCtrl">... your template content...</div>. EDIT: Senang melihat Anda terselesaikan, alangkah baiknya jika Anda memperbarui pertanyaan Anda dengan cara Anda menyelesaikannya, mungkin dengan jsFiddle.
Anders Ekdahl
Saya akan memperbarui ketika saya 100% di sana. Saya mengalami beberapa kebiasaan dengan ruang lingkup di pengendali anak dan ng-klik. Jika saya memuat langsung dari foo, ng-klik dijalankan dalam lingkup foo. Saya kemudian klik pada bilah, dan ng-klik di template itu dieksekusi dalam lingkup induk.
Coder1
1
Perlu dicatat bahwa Anda sebenarnya tidak perlu memformat ulang URL Anda. Ini mungkin menjadi kasus ketika tanggapan diposting, tetapi dokumentasi ( docs.angularjs.org/api/ngRoute.$routeProvider ) sekarang mengatakan: [reloadOnSearch = true] - {boolean =} - memuat ulang rute ketika hanya $ lokasi. search () atau $ location.hash () berubah.
Rhys van der Waerden
93

Jika Anda perlu mengubah path, tambahkan ini setelah .config Anda di file aplikasi Anda. Maka Anda bisa lakukan $location.path('/sampleurl', false);untuk mencegah reload

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

Kredit pergi ke https://www.consolelog.io/angularjs-change-path-without-reloading untuk solusi paling elegan yang saya temukan.

Vigrond
sumber
6
Solusi bagus Apakah ada cara untuk mendapatkan tombol kembali browser untuk "menghormati" ini?
Mark B
6
Ketahuilah bahwa solusi ini menyimpan rute lama, dan jika Anda meminta $ route.current, Anda akan mendapatkan jalur sebelumnya, bukan saat ini. Kalau tidak, ini adalah hack kecil yang hebat.
jornare
4
Hanya ingin mengatakan bahwa solusi asli diposting secara akut oleh "EvanWinstanley" di sini: github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24
2
Saya telah menggunakan solusi ini untuk sementara waktu dan baru saja memperhatikan bahwa pada beberapa perubahan jalur, memuat ulang register sebagai false bahkan ketika nilai sebenarnya dilewatkan. Apakah ada orang lain yang memiliki masalah seperti ini?
Will Hitchcock
2
Saya harus menambahkan $timeout(un, 200);sebelum pernyataan pengembalian karena kadang $locationChangeSuccess- kadang tidak menyala sama sekali apply(dan rute tidak berubah ketika benar-benar diperlukan). Solusi saya membersihkan semuanya setelah doa path (..., false)
snowindy
17

mengapa tidak hanya menempatkan ng-controller satu tingkat lebih tinggi,

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

Dan jangan mengatur pengontrol di rute,

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

ini bekerja untuk saya.

windmaomao
sumber
tip yang bagus, ini bekerja dengan baik untuk skenario saya juga! Terima kasih banyak! :)
daveoncode
4
Ini adalah jawaban "the russians bekas pensil" yang bagus, bekerja untuk saya :)
inolasco
1
Saya berbicara terlalu cepat. Pemecahan masalah ini memiliki masalah bahwa jika Anda perlu memuat nilai dari $ routeParams di controller, mereka tidak akan memuat, default ke {}
inolasco
@inolasco: berfungsi jika Anda membaca $ routeParams di $ routeChangeSuccess handler. Lebih sering daripada tidak, ini mungkin memang yang Anda inginkan. membaca hanya di pengontrol hanya akan memberi Anda nilai untuk URL yang awalnya dimuat.
plong0
@ plong0 Poin bagus, harus bekerja. Namun, dalam kasus saya, saya hanya perlu mendapatkan nilai URL ketika pengontrol memuat setelah memuat halaman, untuk menginisialisasi nilai-nilai tertentu. Saya akhirnya menggunakan solusi yang diusulkan oleh vigrond menggunakan $ locationChangeSuccess, tetapi Anda juga merupakan pilihan lain yang valid.
inolasco
10

Meskipun posting ini sudah tua dan sudah mendapat jawaban, menggunakan reloadOnSeach = false tidak menyelesaikan masalah bagi kita yang perlu mengubah jalur aktual dan bukan hanya params. Inilah solusi sederhana untuk dipertimbangkan:

Gunakan ng-include daripada ng-view dan tetapkan controller Anda di templat.

<!-- 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
})

Hanya sisi buruknya adalah Anda tidak dapat menggunakan atribut resolusinya, tapi itu cukup mudah untuk dilakukan. Anda juga harus mengelola keadaan controller, seperti logika berdasarkan $ routeParams ketika rute berubah dalam controller sebagai perubahan url yang sesuai.

Berikut ini contohnya: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview

Lukus
sumber
1
Tidak yakin bahwa tombol kembali akan berperilaku benar dalam plnkr ... Seperti yang saya sebutkan, Anda harus mengelola beberapa hal sendiri saat rute berubah .. itu bukan solusi yang paling layak dengan kode, tetapi itu berfungsi
Lukus
2

Saya menggunakan solusi ini

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

Pendekatan ini mempertahankan fungsi tombol kembali, dan Anda selalu memiliki routeParams saat ini di templat, tidak seperti beberapa pendekatan lain yang pernah saya lihat.

parlemen
sumber
1

Jawaban di atas, termasuk yang GitHub, memiliki beberapa masalah untuk skenario saya dan juga tombol kembali atau perubahan url langsung dari browser memuat ulang controller, yang saya tidak suka. Saya akhirnya pergi dengan pendekatan berikut:

1. Tetapkan properti dalam definisi rute, yang disebut 'noReload' untuk rute-rute di mana Anda tidak ingin controller memuat ulang pada perubahan rute.

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

2. Dalam menjalankan fungsi modul Anda, masukkan logika yang memeriksa rute-rute tersebut. Ini akan mencegah isi ulang hanya jika noReloadadalah truedan pengendali rute sebelumnya adalah sama.

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. Terakhir, di controller, dengarkan acara $ routeUpdate sehingga Anda dapat melakukan apa pun yang perlu Anda lakukan ketika parameter rute berubah.

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

    //(...)
}

Ingatlah bahwa dengan pendekatan ini, Anda tidak mengubah hal-hal di controller dan kemudian memperbarui jalur yang sesuai. Itu sebaliknya. Anda pertama-tama mengubah jalur, kemudian mendengarkan acara $ routeUpdate untuk mengubah hal-hal di controller ketika parameter rute berubah.

Ini menjaga hal-hal sederhana dan konsisten karena Anda dapat menggunakan logika yang sama baik ketika Anda hanya mengubah path (tetapi tanpa permintaan $ http mahal jika Anda suka) dan ketika Anda benar-benar memuat ulang browser.

CGodo
sumber
1

Ada cara sederhana untuk mengubah jalur tanpa memuat ulang

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

Gunakan kode ini untuk mengubah URL, Halaman tidak akan dialihkan

Parameter kedua "false" sangat penting.

$location.path('/edit_draft_inbox/'+id, false);
Dinesh Vaitage
sumber
ini tidak benar untuk AngularJS docs.angularjs.org/api/ng/service/$location
steampowered
0

Inilah solusi lengkap saya yang memecahkan beberapa hal yang dilewatkan @Vigrond dan @rahilwazir:

  • Ketika params pencarian diubah, itu akan mencegah penyiaran a $routeUpdate.
  • Ketika rute sebenarnya tidak berubah, $locationChangeSuccesstidak pernah dipicu yang menyebabkan pembaruan rute berikutnya dicegah.
  • Jika dalam siklus intisari yang sama ada permintaan pembaruan lain, kali ini yang ingin memuat ulang, pengendali acara akan membatalkan memuat ulang itu.

    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);
            };
        });
    }]);
Oded Niv
sumber
kesalahan ketik pathpada akhirnya seharusnya param, juga ini tidak bekerja dengan hash, dan url di bilah alamat saya tidak diperbarui
deltree
Tetap. Hmm aneh itu bekerja untuk saya meskipun di URL html5. Mungkin masih bisa membantu seseorang, kurasa ...
Oded Niv
0

Tambahkan tag kepala bagian dalam berikut

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

Ini akan mencegah memuat ulang.

Vivek
sumber
0

Sejak sekitar versi 1.2, Anda dapat menggunakan $ location.replace () :

$location.path('/items');
$location.replace();
Brent Washburne
sumber