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にセットしfalsehttps://docs.angularjs.org/api/ngRoute/provider/$routeProvider)。これは、URLの検索部分が変更された場合にビューをリロードしないようにAngularJSに指示します。


それをありがとう。コントローラーをどこで変更するかを考えています。
Coder1 2013

とった。コントローラーのパラメーターをビュー配列に追加ng-controller="view.controller"し、ng-includeディレクティブに追加しました。
Coder1 2013

itemCtrlInitの$ location.search()でウォッチを作成し、検索パラメーターに応じて$ scope.view.templateを更新します。そして、それらのテンプレートはのようなものでラップすることができます<div ng-controller="itemFooCtrl">... your template content...</div>。編集:あなたが解決したことを嬉しく思います、あなたがおそらくあなたがjsFiddleで解決した方法であなたの質問を更新したならそれは素晴らしいでしょう。
Anders Ekdahl、

100%着いたら更新します。子コントローラーとng-clickにスコープがあるいくつかの癖があります。fooから直接ロードすると、ng-clickはfooスコープ内で実行されます。次にバーをクリックすると、そのテンプレートのngクリックが親スコープで実行されます。
Coder1 2013

1
実際にURLを再フォーマットする必要はないことに注意してください。これは応答が投稿されたときに当てはまったかもしれませんが、ドキュメント(docs.angularjs.org/api/ngRoute.$routeProvider)は次のように言っています: [reloadOnSearch = true]-{boolean =}-$ locationのみのときにルートをリロードします。 search()または$ location.hash()の変更。
Rhys van der Waerden 2014

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
素晴らしいソリューション。とにかく、ブラウザの「戻る」ボタンを「これに敬意を表す」ために取得する方法はありますか?
マークB

6
このソリューションでは古いルートが保持されることに注意してください。$ route.currentを要求すると、現在のルートではなく、以前のパスが取得されます。そうでなければ、それは素晴らしい小さなハックです。
jornare 14

4
元のソリューションが「EvanWinstanley」によって実際にここに投稿されたことを言いたかっただけです:github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24

2
私はしばらくの間このソリューションを使用しており、パスが変更されたときに、true値が渡された場合でもレジスタをfalseとしてリロードすることに気づきました。他の誰かがこのような問題を抱えていませんか?
ウィッチヒッチコック、

2
$timeout(un, 200);時々$locationChangeSuccess後でまったく起動しなかったので、returnステートメントの前に追加するapply必要がありました(そして、本当に必要なときにルートは変更されませんでした)。私の解決策は、path(...、false)を呼び出した後、問題をクリアします
snowindy

17

ng-controllerを1レベルだけ高くしないのはなぜですか

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

ルートにコントローラを設定しないでください。

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

わたしにはできる。


素晴らしいヒント、それは私のシナリオでも完全に機能します!どうもありがとうございました!:)
daveoncode 2015年

4
これは「ロシア人が鉛筆を使った」という素晴らしい答えです。私にとっては
うまくいき

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に基づく

PSこのソリューション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;
     });
});

このアプローチでは、戻るボタンの機能が維持され、他のいくつかのアプローチとは異なり、テンプレートには常に現在のrouteParamsが含まれます。


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を変更します。ページはリダイレクトされません

2番目のパラメーター「false」は非常に重要です。

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


0

@Vigrondと@rahilwazirが逃したいくつかの問題を解決する私のより完全なソリューションは次のとおりです。

  • 検索パラメータが変更された場合、のブロードキャストが妨げられました$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では機能します。それでも私が推測する誰かを助ける
かもしれ

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.