คุณสามารถเปลี่ยนเส้นทางโดยไม่โหลดตัวควบคุมใน AngularJS ใหม่ได้หรือไม่?


191

มันเคยถูกถามมาก่อนและจากคำตอบมันดูไม่ดี ฉันต้องการถามด้วยรหัสตัวอย่างนี้เพื่อประกอบการพิจารณา ...

แอพของฉันโหลดรายการปัจจุบันในบริการที่ให้บริการ มีตัวควบคุมหลายตัวที่จัดการกับข้อมูลรายการโดยไม่ต้องโหลดรายการใหม่

ตัวควบคุมของฉันจะโหลดรายการใหม่หากยังไม่ได้ตั้งค่ามิฉะนั้นจะใช้รายการที่โหลดในปัจจุบันจากบริการระหว่างตัวควบคุม

ปัญหา : ฉันต้องการใช้พา ธ ที่แตกต่างกันสำหรับแต่ละคอนโทรลเลอร์โดยไม่ต้องโหลด Item.html ซ้ำ

1) เป็นไปได้ไหม?

2) หากไม่สามารถทำได้มีวิธีที่ดีกว่าในการมีเส้นทางต่อตัวควบคุมเทียบกับสิ่งที่ฉันเกิดขึ้นที่นี่

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

มติ

สถานะ : การใช้คำค้นหาตามคำแนะนำในคำตอบที่ยอมรับทำให้ฉันสามารถระบุ URL ที่แตกต่างกันโดยไม่ต้องโหลดตัวควบคุมหลักอีกครั้ง

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

เนื้อหาภายในบรรจุภัณฑ์ของฉันถูกห่อด้วย ng-controller=...


พบคำตอบนี้: stackoverflow.com/a/18551525/632088 - ใช้ reloadOnSearch: false แต่ยังตรวจสอบการอัปเดต URL ในแถบค้นหา (เช่นหากผู้ใช้คลิกปุ่มย้อนกลับ & เปลี่ยน URL)
robert king

คำตอบ:


115

หากคุณไม่ได้มีการใช้ URL เหมือน#/item/{{item.id}}/fooและ#/item/{{item.id}}/barแต่#/item/{{item.id}}/?fooและ#/item/{{item.id}}/?barแต่คุณสามารถตั้งค่าเส้นทางของคุณสำหรับการ/item/{{item.id}}/ที่จะมีการreloadOnSearchตั้งค่าให้false( https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ) นั่นบอกให้ AngularJS ไม่โหลดมุมมองใหม่หากส่วนการค้นหาของ url เปลี่ยนไป


ขอบคุณสำหรับสิ่งนั้น ฉันพยายามคิดว่าฉันจะเปลี่ยนคอนโทรลเลอร์ที่ไหน
Coder1

เข้าใจแล้ว เพิ่มการควบคุมพารามิเตอร์ในอาร์เรย์มุมมองของฉันแล้วเพิ่มลงng-controller="view.controller"ในng-includeคำสั่ง
Coder1 1

คุณจะสร้างการเฝ้าดู $ location.search () ใน itemCtrlInit และขึ้นอยู่กับพารามิเตอร์การค้นหาอัพเดต $ scope.view.template <div ng-controller="itemFooCtrl">... your template content...</div>และจากนั้นแม่แบบเหล่านั้นอาจถูกห่อด้วยสิ่งที่ชอบ แก้ไข: ยินดีที่ได้เห็นคุณแก้ไขมันจะดีถ้าคุณปรับปรุงคำถามของคุณด้วยวิธีที่คุณแก้ไขอาจจะด้วย jsFiddle
Anders Ekdahl

ฉันจะอัปเดตเมื่อฉันอยู่ที่นั่น 100% ฉันมีนิสัยใจคอกับขอบเขตในตัวควบคุมลูกและ ng-click ถ้าฉันโหลดโดยตรงจาก foo, ng-click จะดำเนินการภายในขอบเขต foo จากนั้นฉันคลิกที่บาร์และคลิก ng ในแม่แบบนั้นจะถูกดำเนินการในขอบเขตหลัก
Coder1 1

1
ควรสังเกตว่าคุณไม่จำเป็นต้องฟอร์แมต URL ของคุณใหม่ นี่อาจเป็นกรณีที่มีการโพสต์ reponse แต่เอกสาร ( docs.angularjs.org/api/ngRoute.$routeProvider ) ตอนนี้พูดว่า: [reloadOnSearch = true] - {boolean =} - โหลดเส้นทางใหม่เมื่อตำแหน่ง $ เท่านั้น การเปลี่ยนแปลงการค้นหา () หรือ $ location.hash ()
Rhys van der Waerden

93

หากคุณต้องการเปลี่ยนเส้นทางให้เพิ่มไฟล์นี้หลังจาก. config ในไฟล์แอพของคุณ จากนั้นคุณสามารถทำได้$location.path('/sampleurl', false);เพื่อป้องกันการโหลดซ้ำ

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

เครดิตไปที่https://www.consolelog.io/angularjs-change-path-without-reloadingสำหรับโซลูชันที่หรูหราที่สุดที่ฉันพบ


6
ทางออกที่ดี มีวิธีรับปุ่มย้อนกลับของเบราว์เซอร์เพื่อ "ให้เกียรติ" สิ่งนี้หรือไม่?
Mark B

6
โปรดทราบว่าโซลูชันนี้จะเก็บเส้นทางเก่าไว้และหากคุณขอ $ route.current คุณจะได้รับเส้นทางก่อนหน้าไม่ใช่เส้นทางปัจจุบัน ไม่อย่างนั้นมันจะเป็นแฮ็คตัวเล็ก ๆ
jornare

4
แค่อยากจะบอกว่าวิธีการแก้ปัญหาเดิมถูกโพสต์อย่างมีระบบโดย "EvanWinstanley" ที่นี่: github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24

2
ฉันใช้วิธีนี้มาระยะหนึ่งแล้วเพิ่งสังเกตุว่าในบางเส้นทางที่มีการเปลี่ยนแปลงให้ทำการรีโหลดลงทะเบียนเป็นเท็จแม้ว่าจะมีการส่งค่าที่แท้จริงแล้วใครก็ตามที่เคยมีปัญหาเช่นนี้?
Will Hitchcock

2
ฉันต้องเพิ่ม$timeout(un, 200);ก่อนที่จะกลับคำสั่งเพราะบางครั้ง$locationChangeSuccessไม่ได้ยิงเลยหลังจากapply(และเส้นทางไม่ได้เปลี่ยนเมื่อจำเป็นจริงๆ) วิธีการแก้ปัญหาของฉันล้างสิ่งต่าง ๆ หลังจากการภาวนาของเส้นทาง (... , เท็จ)
snowindy

17

ทำไมไม่ลองปรับ ng-controller ให้สูงขึ้นหนึ่งระดับ

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

และอย่าตั้งตัวควบคุมในเส้นทาง

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

มันใช้งานได้สำหรับฉัน


เคล็ดลับที่ดีมันทำงานได้อย่างสมบูรณ์แบบสำหรับสถานการณ์ของฉันเช่นกัน! ขอบคุณมาก! :)
daveoncode

4
นี้เป็นใหญ่ "รัสเซียใช้ดินสอ" คำตอบ, การทำงานสำหรับฉัน :)
inolasco

1
ฉันพูดเร็วเกินไป วิธีแก้ปัญหานี้มีปัญหาที่ถ้าคุณต้องการโหลดค่าจาก $ routeParams ในตัวควบคุมพวกเขาจะไม่โหลดค่าเริ่มต้นเป็น {}
inolasco

@inolasco: ใช้งานได้ถ้าคุณอ่าน $ routeParams ใน $ routeChangeSuccess บ่อยกว่านี้อาจเป็นสิ่งที่คุณต้องการอยู่แล้ว การอ่านเฉพาะในคอนโทรลเลอร์จะทำให้คุณรับค่าสำหรับ URL ที่ถูกโหลดไว้ในตอนแรกเท่านั้น
plong0

@ plong0 จุดดีควรทำงาน ในกรณีของฉันฉันเพียงต้องการรับค่า URL เมื่อตัวควบคุมโหลดเมื่อโหลดหน้าเพื่อเริ่มต้นค่าบางอย่าง ฉันลงเอยด้วยการใช้โซลูชันที่เสนอโดย vigrond โดยใช้ $ locationChangeSuccess แต่คุณก็เป็นอีกทางเลือกที่ถูกต้องเช่นกัน
inolasco

12

สำหรับผู้ที่ต้องการเส้นทาง () การเปลี่ยนแปลงโดยไม่ต้องโหลดตัวควบคุม - นี่คือปลั๊กอิน: https://github.com/anglibs/angular-location-update

การใช้งาน:

$location.update_path('/notes/1');

อ้างอิงจากhttps://stackoverflow.com/a/24102139/1751321

ป.ล. โซลูชั่นนี้https://stackoverflow.com/a/24102139/1751321มีบั๊กหลังจากพา ธ (, เท็จ) ที่เรียกว่า - มันจะแยกการนำทางเบราว์เซอร์กลับ / ส่งต่อจนกว่าพา ธ (จริง) เรียกใช้


10

แม้ว่าบทความนี้จะเก่าและได้รับคำตอบที่ยอมรับการใช้ reloadOnSeach = false ไม่ได้แก้ปัญหาสำหรับพวกเราที่ต้องเปลี่ยนเส้นทางจริงไม่ใช่แค่ params นี่เป็นวิธีง่ายๆในการพิจารณา:

ใช้ ng-include แทน ng-view และกำหนดคอนโทรลเลอร์ของคุณในเทมเพลต

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

ข้อเสียเพียงอย่างเดียวคือคุณไม่สามารถใช้แอททริบิวการแก้ไขได้ นอกจากนี้คุณต้องจัดการสถานะของคอนโทรลเลอร์เช่นลอจิกที่อิงกับ $ routeParams เนื่องจากการเปลี่ยนเส้นทางภายในคอนโทรลเลอร์เป็น url ที่สอดคล้องกัน

นี่คือตัวอย่าง: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview


1
ไม่แน่ใจว่าปุ่มย้อนกลับจะทำงานได้อย่างถูกต้องใน plnkr ... ดังที่ได้กล่าวไปแล้วคุณต้องจัดการบางอย่างด้วยตัวเองเนื่องจากการเปลี่ยนเส้นทาง .. มันไม่ใช่วิธีแก้ปัญหาที่คุ้มค่าที่สุดของรหัส แต่มันใช้งานได้
Lukus

2

ฉันใช้วิธีนี้

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

วิธีนี้รักษาฟังก์ชั่นปุ่มย้อนกลับและคุณจะมีเส้นทางพา ธ ปัจจุบันในเทมเพลตเสมอ


1

คำตอบข้างต้นรวมถึง GitHub หนึ่งมีปัญหาบางอย่างสำหรับสถานการณ์ของฉันและปุ่มย้อนกลับหรือการเปลี่ยน URL โดยตรงจากเบราว์เซอร์ก็ทำการโหลดคอนโทรลเลอร์อีกครั้งซึ่งฉันไม่ชอบ ในที่สุดฉันก็ไปตามแนวทางต่อไปนี้:

1.กำหนดคุณสมบัติในคำจำกัดความของเส้นทางที่เรียกว่า 'noReload' สำหรับเส้นทางที่คุณไม่ต้องการให้คอนโทรลเลอร์ทำการโหลดซ้ำเมื่อมีการเปลี่ยนเส้นทาง

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

2.ในฟังก์ชั่นการทำงานของโมดูลของคุณให้วางตรรกะที่ตรวจสอบเส้นทางเหล่านั้น มันจะป้องกันการโหลดซ้ำหากnoReloadเป็นtrueและตัวควบคุมเส้นทางก่อนหน้านี้เหมือนกัน

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.ในที่สุดในคอนโทรลเลอร์ให้ฟังเหตุการณ์ $ routeUpdate เพื่อให้คุณสามารถทำสิ่งที่คุณต้องทำเมื่อเปลี่ยนพารามิเตอร์เส้นทาง

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

    //(...)
}

โปรดทราบว่าด้วยวิธีนี้คุณจะไม่เปลี่ยนแปลงสิ่งต่าง ๆ ในคอนโทรลเลอร์จากนั้นอัปเดตพา ธ ตามลำดับ มันเป็นวิธีอื่น ๆ คุณเปลี่ยนเส้นทางจากนั้นฟังเหตุการณ์ $ routeUpdate เพื่อเปลี่ยนสิ่งต่าง ๆ ในคอนโทรลเลอร์เมื่อพารามิเตอร์เส้นทางเปลี่ยน

สิ่งนี้จะช่วยให้สิ่งต่าง ๆ เรียบง่ายและสอดคล้องกันเนื่องจากคุณสามารถใช้ตรรกะเดียวกันทั้งเมื่อคุณเปลี่ยนเส้นทาง (แต่ไม่ต้องมีการร้องขอ $ http แพงถ้าคุณต้องการ) และเมื่อคุณโหลดเบราว์เซอร์ใหม่ทั้งหมด


1

มีวิธีง่ายๆในการเปลี่ยนเส้นทางโดยไม่ต้องโหลดซ้ำ

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

ใช้รหัสนี้เพื่อเปลี่ยน URL หน้าจะไม่ถูกเปลี่ยนเส้นทาง

พารามิเตอร์ที่สอง "เท็จ" มีความสำคัญมาก

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

สิ่งนี้ไม่ถูกต้องสำหรับ AngularJS docs.angularjs.org/api/ng/service/$location
steampowered

0

นี่คือโซลูชันเต็มรูปแบบของฉันที่แก้ไขบางสิ่งที่ @Vigrond และ @rahilwazir ที่ไม่ได้รับ:

  • เมื่อ params $routeUpdateค้นหามีการเปลี่ยนแปลงก็จะป้องกันไม่ให้ออกอากาศ
  • เมื่อเส้นทางไม่มีการเปลี่ยนแปลงจริง ๆ แล้ว$locationChangeSuccessจะไม่มีการทริกเกอร์ซึ่งทำให้การอัพเดทเส้นทางครั้งต่อไปถูกป้องกัน
  • หากในรอบย่อยเดียวกันมีคำขออัปเดตอีกครั้งคราวนี้ต้องการโหลดใหม่ตัวจัดการเหตุการณ์จะยกเลิกการโหลดซ้ำ

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

พิมพ์ดีดpathตอนท้ายควรจะเป็นparamเช่นนี้จะไม่ทำงานกับแฮชและ url ในแถบที่อยู่ของฉันไม่ได้อัปเดต
deltree

แก้ไขแล้ว. อืมมันทำงานได้ดีสำหรับฉันใน html5 URLs อาจยังคงช่วยใครซักคนที่ฉันเดา ...
Oded Niv

0

เพิ่มแท็กส่วนหัวด้านล่างต่อไปนี้

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

สิ่งนี้จะป้องกันการโหลดซ้ำ


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.