各モデルとそのデータが取得されるまでAngularJSが 新しいルートの表示を遅らせる(Gmailに似た)方法があるのではないかと思います それぞれのサービスを使用します。
たとえば、すべてのプロジェクトを一覧表示するProjectsController
と、これらのプロジェクトを表示するテンプレートであるproject_index.html
がある場合、Project.query()
は新しいページを表示する前に完全に取得されます。
それまでは、古いページは引き続き表示されます(たとえば、別のページを参照していて、このプロジェクトのインデックスを表示することにした場合など)。
$ routeProviderresolve プロパティを使用すると、データが読み込まれるまでルート変更を遅らせることができます。
まず、このようにresolve
属性で経路を定義します。
angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: PhoneListCtrl,
resolve: PhoneListCtrl.resolve}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: PhoneDetailCtrl,
resolve: PhoneDetailCtrl.resolve}).
otherwise({redirectTo: '/phones'});
}]);
resolve
プロパティがroute上で定義されていることに注目してください。
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function(Phone, $q) {
// see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
},
delay: function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
}
コントローラの定義には、コントローラのコンストラクタが利用できるはずのものを宣言するresolveオブジェクトが含まれています。ここではphones
がコントローラに注入され、resolve
プロパティで定義されています。
resolve.phones
関数は約束を返す責任があります。すべての約束が収集され、すべての約束が解決されるまで経路の変更が遅延します。
実用的なデモ: http://mhevery.github.com/angular-phonecat/app /#/ phones 出典: https://github.com/mhevery/angular-phonecat/commmit/ba33d3ec2d01b70eb5d3d531619bf90153496831
これはAngular 1.0.2で動作する最小限の作業例です。
テンプレート:
<script type="text/ng-template" id="/editor-tpl.html">
Editor Template {{datasets}}
</script>
<div ng-view>
</div>
JavaScript:
function MyCtrl($scope, datasets) {
$scope.datasets = datasets;
}
MyCtrl.resolve = {
datasets : function($q, $http) {
var deferred = $q.defer();
$http({method: 'GET', url: '/someUrl'})
.success(function(data) {
deferred.resolve(data)
})
.error(function(data){
//actually you'd want deffered.reject(data) here
//but to show what would happen on success..
deferred.resolve("error value");
});
return deferred.promise;
}
};
var myApp = angular.module('myApp', [], function($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/editor-tpl.html',
controller: MyCtrl,
resolve: MyCtrl.resolve
});
});
合理化されたバージョン:
$ http()はすでにpromise(別名deferred)を返しているので、実際には独自のものを作成する必要はありません。それで、MyCtrlを単純化することができます。解決します。
MyCtrl.resolve = {
datasets : function($http) {
return $http({
method: 'GET',
url: 'http://fiddle.jshell.net/'
});
}
};
$ http()の結果には、データ、ステータス、ヘッダー、configが含まれています。 オブジェクトなので、MyCtrlの本体を次のように変更する必要があります。
$scope.datasets = datasets.data;
私は何人かの人々が最小化にやさしい依存性注入と共にangle.controllerメソッドを使ってこれを行う方法を尋ねているのを見ます。私はこの作業を終えたばかりなので、戻ってきて助けてもらう義務があると感じました。これが私の解決策です(元の質問とMiskoの回答から採用)。
angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: PhoneListCtrl,
resolve: {
phones: ["Phone", "$q", function(Phone, $q) {
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
]
},
delay: ["$q","$defer", function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
]
},
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: PhoneDetailCtrl,
resolve: PhoneDetailCtrl.resolve}).
otherwise({redirectTo: '/phones'});
}]);
angular.controller("PhoneListCtrl", [ "$scope", "phones", ($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}]);
このコードは質問/最も人気のある答えから派生したものであるため、テストされていませんが、縮小対応の角度コードを作成する方法をすでに理解している場合は正しい方向に送られるはずです。私自身のコードが必要としなかった部分の1つは、「電話」の解決機能への「電話」の注入であり、「遅延」オブジェクトもまったく使用していません。
私はこのYouTube動画もお勧めします http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp ちょっと
興味があるなら、私自身のコード(coffeescriptで書かれています)も貼り付けることにしました。
参考までに、私はいくつかのモデルでCRUDを実行するのに役立つ汎用コントローラを使用しています。
appModule.config ['$routeProvider', ($routeProvider) ->
genericControllers = ["boards","teachers","classrooms","students"]
for controllerName in genericControllers
$routeProvider
.when "/#{controllerName}/",
action: 'confirmLogin'
controller: 'GenericController'
controllerName: controllerName
templateUrl: "/static/templates/#{controllerName}.html"
resolve:
items : ["$q", "$route", "$http", ($q, $route, $http) ->
deferred = $q.defer()
controllerName = $route.current.controllerName
$http(
method: "GET"
url: "/api/#{controllerName}/"
)
.success (response) ->
deferred.resolve(response.payload)
.error (response) ->
deferred.reject(response.message)
return deferred.promise
]
$routeProvider
.otherwise
redirectTo: '/'
action: 'checkStatus'
]
appModule.controller "GenericController", ["$scope", "$route", "$http", "$cookies", "items", ($scope, $route, $http, $cookies, items) ->
$scope.items = items
#etc ....
]
このコミット はバージョン1.1.5以降の一部で、$promise
の$resource
オブジェクトを公開します。このコミットを含むngResourceのバージョンはこのようなリソースを解決することを可能にします:
$ routeProvider
resolve: {
data: function(Resource) {
return Resource.get().$promise;
}
}
コントローラ
app.controller('ResourceCtrl', ['$scope', 'data', function($scope, data) {
$scope.data = data;
}]);
このスニペットは依存性注入フレンドリです(私は ngmin および uglify を組み合わせて使用することもできます)。これはよりエレガントなドメイン駆動型ですベースのソリューション。
以下の例では、 Phone リソースと定数 phoneRoutes を登録しています。 )ドメイン。私が提供された答えで好きではなかったものは解決ロジックの場所でした - メインモジュールは知ってはいけませんなにかリソース引数がコントローラに提供される方法に煩わされる。このように、ロジックは同じドメインに留まります。
注: ngmin を使用している場合(および使用していない場合は必要です)、解決関数を記述するだけで済みます。 DI配列規則を使用して
angular.module('myApp').factory('Phone',function ($resource) {
return $resource('/api/phone/:id', {id: '@id'});
}).constant('phoneRoutes', {
'/phone': {
templateUrl: 'app/phone/index.tmpl.html',
controller: 'PhoneIndexController'
},
'/phone/create': {
templateUrl: 'app/phone/edit.tmpl.html',
controller: 'PhoneEditController',
resolve: {
phone: ['$route', 'Phone', function ($route, Phone) {
return new Phone();
}]
}
},
'/phone/edit/:id': {
templateUrl: 'app/phone/edit.tmpl.html',
controller: 'PhoneEditController',
resolve: {
form: ['$route', 'Phone', function ($route, Phone) {
return Phone.get({ id: $route.current.params.id }).$promise;
}]
}
}
});
次の部分は、モジュールが設定状態にあるときにルーティングデータを注入し、それを $ routeProvider に適用することです。
angular.module('myApp').config(function ($routeProvider,
phoneRoutes,
/* ... otherRoutes ... */) {
$routeProvider.when('/', { templateUrl: 'app/main/index.tmpl.html' });
// Loop through all paths provided by the injected route data.
angular.forEach(phoneRoutes, function(routeData, path) {
$routeProvider.when(path, routeData);
});
$routeProvider.otherwise({ redirectTo: '/' });
});
この設定でルート設定をテストすることはまた非常に簡単です:
describe('phoneRoutes', function() {
it('should match route configuration', function() {
module('myApp');
// Mock the Phone resource
function PhoneMock() {}
PhoneMock.get = function() { return {}; };
module(function($provide) {
$provide.value('Phone', FormMock);
});
inject(function($route, $location, $rootScope, phoneRoutes) {
angular.forEach(phoneRoutes, function (routeData, path) {
$location.path(path);
$rootScope.$digest();
expect($route.current.templateUrl).toBe(routeData.templateUrl);
expect($route.current.controller).toBe(routeData.controller);
});
});
});
});
私の最新の(近日中の)実験 であなたはそれを完全に栄光で見ることができます。この方法は私にはうまくいきますが、$ =インジェクタが何かのインジェクションを検出したときに何かの構築を遅らせないのは本当に不思議です。 約束 オブジェクト。それは物事をとても簡単にするだろう。
編集:used Angular v1.2(rc2)
ルートの表示を遅らせることは非同期のもつれにつながることは間違いありません...あなたのメインエンティティのロードステータスを単に追跡してビューでそれを使わないのはなぜでしょう。例えばあなたのコントローラではngResourceで成功とエラーコールバックの両方を使うかもしれません:
$scope.httpStatus = 0; // in progress
$scope.projects = $resource.query('/projects', function() {
$scope.httpStatus = 200;
}, function(response) {
$scope.httpStatus = response.status;
});
それから、ビューであなたは何でもすることができました:
<div ng-show="httpStatus == 0">
Loading
</div>
<div ng-show="httpStatus == 200">
Real stuff
<div ng-repeat="project in projects">
...
</div>
</div>
<div ng-show="httpStatus >= 400">
Error, not found, etc. Could distinguish 4xx not found from
5xx server error even.
</div>
AngularJS 1.1.5の構文を使用して、Justenの回答の「電話」機能を更新する。
元の:
phones: function($q, Phone) {
var deferred = $q.defer();
Phone.query(function(phones) {
deferred.resolve(phones);
});
return deferred.promise;
}
更新しました:
phones: function(Phone) {
return Phone.query().$promise;
}
Angularチームと貢献者のおかげで、はるかに短くなりました。 :)
これもマキシミリアンホフマンの答えです。どうやらそのコミットは1.1.5にそれを作りました。
私は上記のMiskoのコードを使って作業しましたが、これが私がやったことです。 $defer
が$timeout
に変更されたので、これはより最新の解決策です。ただし、$timeout
を代入すると、タイムアウト期間(Miskoのコードでは1秒)が経過した後、時間内に解決されることを期待してデータが返されます。このようにして、それはできるだけ早く返します。
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function($q, Phone) {
var deferred = $q.defer();
Phone.query(function(phones) {
deferred.resolve(phones);
});
return deferred.promise;
}
}
データがロードされるまでルートの変更を遅らせるには、 $ routeProvider resolve プロパティを使用します。
angular.module('app', ['ngRoute']).
config(['$routeProvider', function($routeProvider, EntitiesCtrlResolve, EntityCtrlResolve) {
$routeProvider.
when('/entities', {
templateUrl: 'entities.html',
controller: 'EntitiesCtrl',
resolve: EntitiesCtrlResolve
}).
when('/entity/:entityId', {
templateUrl: 'entity.html',
controller: 'EntityCtrl',
resolve: EntityCtrlResolve
}).
otherwise({redirectTo: '/entities'});
}]);
resolve
プロパティがroute上で定義されていることに注目してください。
EntitiesCtrlResolve
およびEntityCtrlResolve
は、 定数 オブジェクトがEntitiesCtrl
およびEntityCtrl
コントローラと同じファイルに定義されていることです。
// EntitiesCtrl.js
angular.module('app').constant('EntitiesCtrlResolve', {
Entities: function(EntitiesService) {
return EntitiesService.getAll();
}
});
angular.module('app').controller('EntitiesCtrl', function(Entities) {
$scope.entities = Entities;
// some code..
});
// EntityCtrl.js
angular.module('app').constant('EntityCtrlResolve', {
Entity: function($route, EntitiesService) {
return EntitiesService.getById($route.current.params.projectId);
}
});
angular.module('app').controller('EntityCtrl', function(Entity) {
$scope.entity = Entity;
// some code..
});
私はdarkporterのアイデアが好きです。AngularJSに不慣れな開発チームにとっては理解しやすく、すぐにうまくいくことが容易だからです。
私は2つのdivを使用するこの適応を作成しました。1つはローダーバー用、もう1つはデータがロードされた後に表示される実際のコンテンツ用です。エラー処理は他の場所で行われます。
$ scopeに 'ready'フラグを追加してください。
$http({method: 'GET', url: '...'}).
success(function(data, status, headers, config) {
$scope.dataForView = data;
$scope.ready = true; // <-- set true after loaded
})
});
HTMLビューで:
<div ng-show="!ready">
<!-- Show loading graphic, e.g. Twitter Boostrap progress bar -->
<div class="progress progress-striped active">
<div class="bar" style="width: 100%;"></div>
</div>
</div>
<div ng-show="ready">
<!-- Real content goes here and will appear after loading -->
</div>
Boostrapプログレスバーのドキュメント もご覧ください。
私は上記の答えが好きで、それらから多くを学びましたが、上記の答えのほとんどに欠けているものがあります。
私は、サーバーからの最初のリクエストで取得されたデータを使ってURLを解決するという同様のシナリオで立ち往生していました。 約束がrejected
の場合、私が直面した問題は何でしたか?
私は、設定段階で$routeProvider
のPromise
によって解決されていたresolve
を返すカスタムプロバイダーを使用していました。
私がここで強調したいのは、それがこのようなことをするwhen
の概念です。
それはurlバーの中のurlを見て、そして呼ばれたコントローラとビューのそれぞれのwhen
ブロックは今のところとても良い参照されています。
次の設定フェーズコードがあるとしましょう。
App.when('/', {
templateUrl: '/assets/campaigns/index.html',
controller: 'CampaignListCtr',
resolve : {
Auth : function(){
return AuthServiceProvider.auth('campaign');
}
}
})
// Default route
.otherwise({
redirectTo: '/segments'
});
ブラウザのルートURLで最初の実行ブロックが呼び出されます。それ以外の場合はotherwise
が呼び出されます。
私がアドレスバーでrootUrlをヒットしたシナリオを想像してみましょうAuthServicePrivider.auth()
関数が呼び出されます。
Promiseが返されたreject状態になったとしましょう???
何もレンダリングされません。
Otherwise
ブロックは、configブロックで定義されておらず、angularjs設定フェーズでは未知のURLに対しては実行されません。
この約束が解決されないときに発生するイベントを処理する必要があります。失敗すると$routeChangeErorr
は$rootScope
で起動されます。
以下のコードに示すようにそれをキャプチャすることができます。
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
// Use params in redirection logic.
// event is the routeChangeEvent
// current is the current url
// previous is the previous url
$location.path($rootScope.rootPath);
});
IMO一般に、イベント追跡コードをアプリケーションの実行ブロックに入れることをお勧めします。このコードはアプリケーションの設定フェーズの直後に実行されます。
App.run(['$routeParams', '$rootScope', '$location', function($routeParams, $rootScope, $location){
$rootScope.rootPath = "my custom path";
// Event to listen to all the routeChangeErrors raised
// by the resolve in config part of application
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
// I am redirecting to rootPath I have set above.
$location.path($rootScope.rootPath);
});
}]);
このようにして、設定段階で約束の失敗を処理することができます。
私は無効化されたスクリーンレイヤーを持つ複雑なマルチレベルスライディングパネルインターフェースを持っていました。次のような状態を実行するためのクリックイベントを作成する無効化された画面レイヤーにディレクティブを作成する
$state.go('account.stream.social.view');
フリック効果を生み出していました。代わりにhistory.back()を使用しても問題ありませんでしたが、私の場合は常に履歴に戻るわけではありませんでした。 SO私が知っているのは、state.goの代わりに自分の無効化画面に単にhref属性を作成すれば、魅力のように機能したということです。
<a class="disable-screen" back></a>
ディレクティブ 'back'
app.directive('back', [ '$rootScope', function($rootScope) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
element.attr('href', $rootScope.previousState.replace(/\./gi, '/'));
}
};
} ]);
app.js前の状態を保存するだけです
app.run(function($rootScope, $state) {
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
$rootScope.previousState = fromState.name;
$rootScope.currentState = toState.name;
});
});