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 ). URL의 검색 부분이 변경되면 AngularJS가 뷰를 다시로드하지 않도록 지시합니다.


고마워 컨트롤러를 어디에서 바꿀지 알아 내려고합니다.
Coder1

알았다. 내 뷰 배열에 컨트롤러 매개 변수를 추가 한 다음 지시문에 추가 ng-controller="view.controller"했습니다 ng-include.
Coder1

itemCtrlInit의 $ location.search ()에서 watch를 작성하고 검색 매개 변수에 따라 $ scope.view.template을 업데이트하십시오. 그런 다음 해당 템플릿을 다음과 같이 포장 할 수 있습니다 <div ng-controller="itemFooCtrl">... your template content...</div>. 편집 : 당신이 해결 된 것을 반갑습니다 .jsFiddle을 사용하여 해결 한 방법으로 질문을 업데이트하면 좋을 것입니다.
Anders Ekdahl

100 %가되면 업데이트하겠습니다. 자식 컨트롤러에 범위가있는 단점이 있고 ng-click입니다. foo에서 직접로드하면 ng-click이 foo 범위 내에서 실행됩니다. 그런 다음 막대를 클릭하면 해당 템플릿의 ng- 클릭이 상위 범위에서 실행됩니다.
Coder1

1
실제로 URL을 다시 포맷 할 필요는 없습니다. 응답이 게시 된 경우 일 수 있지만 설명서 ( docs.angularjs.org/api/ngRoute.$routeProvider )에 이제 다음과 같이 표시됩니다. [reloadOnSearch = true]-{boolean =}-$ location 만있는 경우 경로를 다시로드합니다. search () 또는 $ 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
: 그냥 원래의 솔루션이 acutally 여기에 "EvanWinstanley"에 의해 게시 한 말하고 싶었다 github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24

2
나는이 솔루션을 잠시 동안 사용했으며 일부 경로 변경시 실제 값이 전달 된 경우에도 레지스터를 false로 다시로드한다는 것을 알았습니다. 다른 사람이 이와 같은 문제가 있습니까?
Will Hitchcock

2
$timeout(un, 200);때로는 때때로 $locationChangeSuccess전혀 발생하지 않았기 때문에 return return 문 을 추가 해야했습니다 apply(실제로 필요할 때 경로가 변경되지 않았습니다). 내 솔루션은 path (..., false)를 호출 한 후
정리합니다.

17

왜 ng-controller를 한 단계 높이 올리지 않겠습니까?

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

경로에 컨트롤러를 설정하지 마십시오.

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

그것은 나를 위해 작동합니다.


좋은 팁, 그것은 내 시나리오에도 완벽하게 작동합니다! 대단히 감사합니다! :)
daveoncode

4
이것은 훌륭한 답변 "러시아는 연필을 사용"입니다 :) 나를 위해 작동
inolasco

1
너무 빨리 말 했어요 이 해결 방법은 컨트롤러에서 $ routeParams의 값을로드해야하는 경우 기본값으로 {}로로드되지 않습니다.
inolasco

@inolasco : $ routeChangeSuccess 핸들러에서 $ routeParams를 읽는 경우 작동합니다. 어쨌든 이것은 아마도 당신이 원하는 것입니다. 컨트롤러에서 읽기만하면 원래로드 된 URL의 값만 얻을 수 있습니다.
plong0

@ plong0 좋은 지적, 작동합니다. 내 경우에는 특정 값을 초기화하기 위해 컨트롤러가 페이지로드시로드 될 때 URL 값만 가져와야했습니다. 나는 $ locationChangeSuccess를 사용하여 vigrond가 제안한 솔루션을 사용했지만 결국 다른 유효한 옵션입니다.
inolasco

12

컨트롤러를 다시로드하지 않고 path () 변경이 필요한 사람들을 위해-여기 플러그인이 있습니다 : https://github.com/anglibs/angular-location-update

용법:

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

https://stackoverflow.com/a/24102139/1751321 기반

추신 :이 솔루션 https://stackoverflow.com/a/24102139/1751321 path (, false) 호출 후 버그가 포함되어 있습니다-path (, true)가 호출 될 때까지 브라우저 탐색을 앞뒤로 중단시킵니다.


10

이 게시물은 오래되었지만 대답이 수락되었지만 reloadOnSeach = false를 사용해도 매개 변수뿐만 아니라 실제 경로를 변경 해야하는 사람들에게는 문제가 해결되지 않습니다. 고려해야 할 간단한 해결책은 다음과 같습니다.

ng-view 대신 ng-include를 사용하고 템플릿에 컨트롤러를 할당하십시오.

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

유일한 단점은 resolve 속성을 사용할 수 없지만 해결하기가 쉽다는 것입니다. 또한 해당 URL이 변경됨에 따라 경로가 컨트롤러 내에서 변경 될 때 $ routeParams 기반 논리와 같이 컨트롤러의 상태를 관리해야합니다.

예를 들면 다음과 같습니다. 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;
     });
});

이 방법은 뒤로 버튼 기능을 유지하며 내가 본 다른 방법과 달리 항상 템플릿에 현재 routeParam이 있습니다.


1

GitHub 하나를 포함하여 위의 답변에는 시나리오에 문제가 있었고 브라우저에서 뒤로 버튼 또는 URL 직접 URL 변경으로 인해 컨트롤러가 다시로드되었습니다. 마침내 다음과 같은 접근 방식을 사용했습니다.

1. 경로 변경시 컨트롤러를 다시로드하지 않으려는 경로에 대해 경로 정의에서 'noReload'라는 속성을 정의하십시오.

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

2. 모듈의 실행 기능에서 해당 경로를 확인하는 논리를 입력하십시오. noReloadis 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을 변경하면 페이지가 리디렉션되지 않습니다

두 번째 매개 변수 "false"는 매우 중요합니다.

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

이 AngularJS와의에 대한 정확하지 docs.angularjs.org/api/ng/service/$location
steampowered

0

@Vigrond와 @rahilwazir가 놓친 몇 가지 사항을 해결하는 풀러 솔루션은 다음과 같습니다.

  • 검색 매개 변수가 변경되면 a 방송이 차단 $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 URL에서 작동합니다. 아직도 내가 생각하는 사람을 도울 수 있습니다 ...
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.