web-dev-qa-db-ja.com

AngularJS:コントローラーを完全にリロードせずにハッシュとルートを変更する

次のようなルートのコントローラーがあります。

#/ articles/1234

コントローラーを完全にリロードせずにルートを変更したいので、コントローラー内の他の要素の位置を一定に保つことができます(スクロールするリスト)

私はこれを行ういくつかの方法を考えることができますが、それらはすべてかなりprettyいです。このようなことをするためのベストプラクティスはありますか? reloadOnSearch:falseを試してみましたが、何も変わらないようです。

35
doubledriscoll

まったく同じ挑戦を持っていた、

別のStackOverflowレスポンス でハッキングを発見しました。

かなりきれいなソリューション-$ location.pathを設定するコントローラーにこれらの行を追加するだけでした:

var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
    $route.current = lastRoute;
});

..もちろん、コントローラーに$ routeが挿入されるようにしました。

それでも、「DoNotFollowRoutesOnPathChange」はAngularJSの欠落している機能のように感じます。

/イェンス

Update:このイベントをリッスンすると、$ routeProvider configsの使用が効果的に停止されるため、このキャッチを現在のパスのみに制限する必要がありました。

    var lastRoute = $route.current;
    if ($route.current.$route.templateUrl.indexOf('mycurrentpath') > 0) {
        $route.current = lastRoute;         
    }

Gettingい...

34

簡単な回答:

前述のように、$location.search()メソッドを使用できます。他のルートイベントではなく、スコープで_"$routeUpdate"_イベントをリッスンする必要があります。 $ route API

説明:

  1. まず最初に(すでに知っていますが)_reloadOnSearch: false_を_$routeProvider_に追加します:

    _$routeProvider.when('/somewhere', {
        controller: 'SomeCtrl',
        reloadOnSearch: false
    })
    _
  2. アンカータグhrefまたは_ng-href_を_href="#/somewhere?param=value"_に変更すると、パス部分(_$routeChangeSuccess_)が現在の場所と異なる場合に_/somewhere_イベントがトリガーされます。それ以外の場合は、_$routeUpdate_イベントをトリガーします。

  3. スコープでイベントをリッスンする:

    _$scope.$on("$routeUpdate", function(event, route) {
        // some code here
    });
    _
  4. コード内の検索パラメーターを変更する場合は、$location.search()メソッドを使用できます。 $ location.search API

21
Daiwei

ReloadOnSearchをfalseに設定した場合、?a=b&c=dリロードなしのURLの部分。ただし、リロードせずに実際の場所をきれいに変更することはできません。

8
Andrew Joslin

編集

NgRouteを使用する場合のより良いアプローチ:

/**
 * Add skipReload() to $location service.
 *
 * See https://github.com/angular/angular.js/issues/1699
 */
app.factory('location',
  ['$rootScope', '$route', '$location',
  function($rootScope, $route, $location) {

  $location.skipReload = function() {
    var lastRoute = $route.current;

    var deregister = $rootScope.$on('$locationChangeSuccess',
                                    function(e, absUrl, oldUrl) {
      console.log('location.skipReload', 'absUrl:', absUrl, 'oldUrl:', oldUrl);
      $route.current = lastRoute;
      deregister();
    });

    return $location;
  };

  return $location;
}]);

使い方:

app.controller('MyCtrl', ['$scope', 'location', function($scope, location) {
  $scope.submit = function() {
    location.skipReload().path(path);
  };
}]);

古い答え

Jens X Augustssonの回答に基づいて、再利用可能なファクトリを作成しました。

app.factory('DoNotReloadCurrentTemplate', ['$route', function($route) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate',
                    $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

AngularJS 1.0.6で動作します

使い方:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);
}]);

ここにAngularJSの問題: https://github.com/angular/angular.js/issues/1699

7
tanguy_k

使用する:

$routeProvider.when('/somewhere', {
    controller: 'SomeCtrl',
    reloadOnSearch: false
})

これにより、クエリパラメータが変更されたときにコントローラーがリロードされるのを防ぎますが、ハッシュが変更されたときにもリロードされます。

1
ehmicky

そのようにHTML5のwindow.historyを処理するファクトリを定義します(いくつかの問題があるため、Androidでも動作するように独自のStackを保持しています):

.factory('History', function($rootScope) {
    var StateQueue = [];
    var StatePointer = 0;
    var State = undefined;
    window.onpopstate = function(){
        // called when back button is pressed
        State = StateQueue.pop();
        State = (State)?State:{};
        StatePointer = (StatePointer)?StatePointer-1:0;
        $rootScope.$broadcast('historyStateChanged', State);
        window.onpopstate = window.onpopstate;

    }
    return {
        replaceState : function(data, title, url) {
            // replace current state
            var State = this.state();
            State = {state : data};
            window.history.replaceState(State,title,url);
            StateQueue[StatePointer] = State;
            $rootScope.$broadcast('historyStateChanged', this.state());
        },
        pushState : function(data, title, url){
            // Push state and increase pointer
            var State = this.state();
            State = {state : data};
            window.history.pushState(State,title,url);
            StateQueue.Push(State);
            $rootScope.$broadcast('historyStateChanged', this.state());
            StatePointer ++;
        },
        fakePush : function(data, title, url) {
            // call this when you do location.url(url)
            StateQueue.Push((StateQueue.length - 1 >= 0)?StateQueue[StateQueue.length -1]:{});
            StatePointer ++;
            $rootScope.$broadcast('historyStateChanged', this.state());
        },
        state : function() {
            // get current state
            return (StateQueue[StatePointer])?StateQueue[StatePointer]:{};
        },
        popState : function(data) {
            // TODO manually popState
        },
        back : function(data) {
            // TODO needed for iphone support since iphone doesnt have a back button
        }
    }
})

依存スコープにいくつかのリスナーを追加すると、次のようになります。

$scope.$on('historyStateChanged', function(e,v) {
        if(v)
            $scope.state = v;
        else
            $scope.state = {}
    });

それが私のやり方です。私の観点では、URLは新しいビューがロードされたときにのみ変更されるはずです。とにかくAngularチームがそれを意図した方法だと思います。モデルの進む/戻るボタンをマップしたい場合は、HTML5のwindow.historyでそれを試してみてください。

私が助けてくれることを願っています。乾杯、ハインリッヒ

1
Heinrich

あなたが2015年にここに着いた場合:ここでの本当の答えは、これらのハックを使用しないことです(上記の方法のいずれかを使用すると、resolveなどを使用する可能性が失われるため、私はそれらをハックします)が、 i-router

こちら 違いについての便利なプレゼンテーション。実装は、$ routeを$ stateに交換し、状態を名前に変換するだけの簡単なものにする必要があります。

私は現在、ルートへのhrefを参照するメソッドに切り替えています。オプションのgetパラメーターを使用して、リロードせずに状態を変更します。詳細については、「params」セクションをご覧ください here

1
SchizoDuckie

$locationChange~およびHistoryStateハックなしでこれを行うには、routes resolve promiseオプションを使用します。

articleルートがあり、その番号が変更されたと仮定すると、これを行うことができます。

$routeProvider.when(
    '/article/:number',
    {
        templateUrl : 'partial.html',
        controller : 'ArticleCtrl',
        resolve : {
            load : ['$q', '$routeParams', function($q, $routeParams) {
                var defer = $q.defer();

                //number was specified in the previous route parameters, means we were already on the article page
                if ($routeParams.number)
                    //so dont reload the view
                    defer.reject('');
                //otherwise, the number argument was missing, we came to this location from another route, load the view
                else
                    defer.resolve();

                return defer.promise;
            }]
        }
    }
);
0
Hashbrown

このシンプルなソリューションは(驚くべきことに)私にとってはうまくいきます:

$state.params.id = id;  //updating the $state.params somehow prevents reloading the state
$location.path('/articles/' + id);

ただし、[戻る]ボタンと[進む]ボタンで状態が再読み込みされるのを防ぎません。注:angularjs 1.2.7とui-router 0.0.1を使用しています(古いことは知っています)。

0
elfan

プラグインは次のとおりです。 https://github.com/anglibs/angular-location-update

使用法:

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