两个控制器依赖于异步返回的相同数据
Two controllers depending on same data returned asynchronously
有一个允许用户创建或加入房间的应用程序。因此 RoomsListController
显示房间,RoomDetailsController
显示特定房间内 activity 的内容和控件。当然,RoomService
提供了 getRooms()
(异步)方法。
现在 Angular 使两个控制器等待 RoomService.getRooms()
返回的相同数据的方法是什么?
详情:
- 应用程序中有身份验证,房间列表取决于 userId。所以在应用程序启动时调用
RoomService.getRooms()
不是一个选项
RoomDetailsController
并不总是请求房间列表。只要通过 $stateParams
提供的房间存在并且处于活动状态,它就会简单地显示出来。如果没有,会有一些逻辑将用户引导到另一个房间(这里需要房间列表)或显示一条消息,指出没有可用的房间,也许是时候创建一个了。
- 让我们假设
getRooms()
对服务器来说是一项繁重的操作,并且当可以重复使用客户端上的相同数据时,不希望得到它两次。
- 希望有一个不依赖于先执行哪个控制器功能的逻辑,因为这可能会受到模板中布局更新的影响
您将在路由解析之前调用异步方法:
// in your config
$routeProvider.when('/your-route', {
resolve: {
roomData: function(RoomService) {
return RoomService.getRooms();
}
}
}
// RoomsListController
function(roomData) {
// do something with roomData
}
// RoomDetailsController
function(roomData) {
// do something with roomData
}
这样,就控制器而言,数据甚至在路由加载之前就已经存在。
您谈到了一个关键问题是应用程序...也就是说,为以应用程序可以使用的方式组合的数据创建单一真实来源。
有很多模式可以解决这个问题,但我建议先看看路由解析承诺,然后再从那里扩展。它可以变得非常复杂。
另一种方法可能是修改服务中的 RoomService.getRooms()
函数,以便在您等待来自服务器的结果时,在首次调用该函数时声明和存储本地承诺。如果再次调用该函数,您将立即 return 相同的承诺。从服务器 return 获取结果后,您可以解决存储的承诺并清除它,因此下次调用它时,将发出新请求。
当然,这里有足够的自定义空间。如果您知道来自服务器的数据永远不会改变,例如,您根本不需要清除承诺。每次有人要房间时,只需 return 即可。
这是一个示例实现:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, RoomService) {
$scope.name = 'World';
$scope.fetchingStatus = function() {
return RoomService.serverRequests ? 'Fetching... Server Requests: ' + RoomService.serverRequests : '';
};
$scope.getRooms = function() {
console.log('asking for rooms')
RoomService.getRooms().then(function(rooms) {
console.log('rooms returned: ', rooms);
$scope.rooms = rooms;
});
};
});
function RoomService($q, $timeout) {
this.$q = $q;
this.$timeout = $timeout;
this.getRoomsPromise = null;
this.serverRequests = 0;
}
RoomService.prototype.getRooms = function() {
if (!this.getRoomsPromise) {
this.getRoomsPromise = this.$q.defer();
this.getRoomsFromServer();
}
return this.getRoomsPromise.promise;
};
RoomService.prototype.getRoomsFromServer = function() {
var rooms = [];
for (var i = 1; i <= 10; i++) {
rooms.push({
id: i,
name: 'Room ' + i
});
}
console.log('fetching data from server...');
this.serverRequests++;
this.$timeout(function() {
this.serverRequests--;
this.getRoomsPromise.resolve(rooms);
this.getRoomsPromise = null;
console.log('got data from server...')
}.bind(this), 1500);
}
app.service('RoomService', RoomService);
.fetching {
background-color: red;
color: white;
padding: 2px 10px;
display: inline-block;
margin-left: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<div ng-app="plunker" ng-controller="MainCtrl">
<p>
<button ng-click="getRooms()">Get Rooms</button>
<span class="fetching" ng-show="fetchingStatus()">{{fetchingStatus()}}</span>
</p>
<ul>
<li ng-repeat="room in rooms track by room.id">{{room.name}}</li>
</ul>
</div>
有一个允许用户创建或加入房间的应用程序。因此 RoomsListController
显示房间,RoomDetailsController
显示特定房间内 activity 的内容和控件。当然,RoomService
提供了 getRooms()
(异步)方法。
现在 Angular 使两个控制器等待 RoomService.getRooms()
返回的相同数据的方法是什么?
详情:
- 应用程序中有身份验证,房间列表取决于 userId。所以在应用程序启动时调用
RoomService.getRooms()
不是一个选项 RoomDetailsController
并不总是请求房间列表。只要通过$stateParams
提供的房间存在并且处于活动状态,它就会简单地显示出来。如果没有,会有一些逻辑将用户引导到另一个房间(这里需要房间列表)或显示一条消息,指出没有可用的房间,也许是时候创建一个了。- 让我们假设
getRooms()
对服务器来说是一项繁重的操作,并且当可以重复使用客户端上的相同数据时,不希望得到它两次。 - 希望有一个不依赖于先执行哪个控制器功能的逻辑,因为这可能会受到模板中布局更新的影响
您将在路由解析之前调用异步方法:
// in your config
$routeProvider.when('/your-route', {
resolve: {
roomData: function(RoomService) {
return RoomService.getRooms();
}
}
}
// RoomsListController
function(roomData) {
// do something with roomData
}
// RoomDetailsController
function(roomData) {
// do something with roomData
}
这样,就控制器而言,数据甚至在路由加载之前就已经存在。
您谈到了一个关键问题是应用程序...也就是说,为以应用程序可以使用的方式组合的数据创建单一真实来源。
有很多模式可以解决这个问题,但我建议先看看路由解析承诺,然后再从那里扩展。它可以变得非常复杂。
另一种方法可能是修改服务中的 RoomService.getRooms()
函数,以便在您等待来自服务器的结果时,在首次调用该函数时声明和存储本地承诺。如果再次调用该函数,您将立即 return 相同的承诺。从服务器 return 获取结果后,您可以解决存储的承诺并清除它,因此下次调用它时,将发出新请求。
当然,这里有足够的自定义空间。如果您知道来自服务器的数据永远不会改变,例如,您根本不需要清除承诺。每次有人要房间时,只需 return 即可。
这是一个示例实现:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, RoomService) {
$scope.name = 'World';
$scope.fetchingStatus = function() {
return RoomService.serverRequests ? 'Fetching... Server Requests: ' + RoomService.serverRequests : '';
};
$scope.getRooms = function() {
console.log('asking for rooms')
RoomService.getRooms().then(function(rooms) {
console.log('rooms returned: ', rooms);
$scope.rooms = rooms;
});
};
});
function RoomService($q, $timeout) {
this.$q = $q;
this.$timeout = $timeout;
this.getRoomsPromise = null;
this.serverRequests = 0;
}
RoomService.prototype.getRooms = function() {
if (!this.getRoomsPromise) {
this.getRoomsPromise = this.$q.defer();
this.getRoomsFromServer();
}
return this.getRoomsPromise.promise;
};
RoomService.prototype.getRoomsFromServer = function() {
var rooms = [];
for (var i = 1; i <= 10; i++) {
rooms.push({
id: i,
name: 'Room ' + i
});
}
console.log('fetching data from server...');
this.serverRequests++;
this.$timeout(function() {
this.serverRequests--;
this.getRoomsPromise.resolve(rooms);
this.getRoomsPromise = null;
console.log('got data from server...')
}.bind(this), 1500);
}
app.service('RoomService', RoomService);
.fetching {
background-color: red;
color: white;
padding: 2px 10px;
display: inline-block;
margin-left: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<div ng-app="plunker" ng-controller="MainCtrl">
<p>
<button ng-click="getRooms()">Get Rooms</button>
<span class="fetching" ng-show="fetchingStatus()">{{fetchingStatus()}}</span>
</p>
<ul>
<li ng-repeat="room in rooms track by room.id">{{room.name}}</li>
</ul>
</div>