How do we access the data from the resolve function without relading the controller?
如何在不重置控制器的情况下从解析功能访问数据?
We are currently working on a project which uses angular-ui-router
. We have two seperated views: on the left a list of parent elements, on the right that elements child data.
我们目前正在开发一个使用angular-ui-router的项目。我们有两个单独的视图:左边是父元素列表,右边是元素子数据。
If selecting a parent on the left, we resolve it's child data to the child-view on the right.
如果在左侧选择父级,我们将其子级数据解析为右侧的子视图。
With the goal not to reaload the childs controller (and view), when selecting a different parent element, we set notify:false
.
为了不实现子控制器(和视图)的目标,在选择不同的父元素时,我们设置notify:false。
We managed to 're-resolve' the child controllers data while not reloading the controller and view, but the data (scope) won't refresh.
我们设法在不重新加载控制器和视图的情况下“重新解析”子控制器数据,但数据(范围)不会刷新。
We did a small plunker to demonstrate our problem here
我们做了一个小傻瓜来证明我们的问题
First click on a number to instantiate the controllers childCtrl
. Every following click should change the child scopes data - which does not work. You might notice the alert
output already has the refreshed data we want to display.
首先单击一个数字以实例化控制器childCtrl。每次跟随点击都应该更改子范围数据 - 这不起作用。您可能会注意到警报输出已经包含我们要显示的刷新数据。
3 个解决方案
#1
1
Not so pretty, but working solution would be to use events. Well, maybe it is not that bad, at least it is not complicated. https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview
不太好,但工作解决方案是使用事件。好吧,也许它不是那么糟糕,至少它并不复杂。 https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview
angular.module('app',[
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views:{
'parent':{
controller: 'parentCtrl',
template: '<div id="parent">'+
'<button ng-click="go(1)">1</button><br>'+
'<button ng-click="go(2)">2</button><br>'+
'<button ng-click="go(3)">3</button><br>'+
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views:{
'child@':{
controller: 'childCtrl',
template:'<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: function($http, $stateParams, $rootScope) {
return $http.get('file'+$stateParams.id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
$rootScope.$broadcast('newData', response.data);
return response.data;
});
}
}
});
})
.controller('parentCtrl', function ($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function (id) {
$state.go('parent.child', {id: id}, {notify:notify});
notify = false;
};
})
.controller('childCtrl', function ($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.$on('newData', function (event, detailResolver) {
$scope.id = detailResolver;
});
$scope.id = detailResolver;
$interval(function(){
console.log(detailResolver.id)
},1000)
})
;
EDIT: A little bit more complicated solution, that requires changing promise creator function into observables, but works: https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p=preview
编辑:一个更复杂的解决方案,需要将promise创建者函数更改为observables,但有效:https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p = preview
angular.module('app', [
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views: {
'parent': {
controller: 'parentCtrl',
template: '<div id="parent">' +
'<button ng-click="go(1)">1</button><br>' +
'<button ng-click="go(2)">2</button><br>' +
'<button ng-click="go(3)">3</button><br>' +
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views: {
'child@': {
controller: 'childCtrl',
template: '<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: turnToObservable(['$http', '$stateParams', function($http, $stateParams) { //Have to be decorated either be this or $inject
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
return response.data;
});
}])
}
});
})
.controller('parentCtrl', function($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function(id) {
$state.go('parent.child', {id: id}, {notify: notify});
notify = false;
};
})
.controller('childCtrl', function($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
detailResolver.addListener(function (id) {
$scope.id = id;
});
});
function turnToObservable(promiseMaker) {
var promiseFn = extractPromiseFn(promiseMaker);
var listeners = [];
function addListener(listener) {
listeners.push(listener);
return function() {
listeners = listeners.filter(function(other) {
other !== listener;
});
}
}
function fireListeners(result) {
listeners.forEach(function(listener) {
listener(result);
});
}
function createObservable() {
promiseFn.apply(null, arguments).then(fireListeners);
return {
addListener: addListener
};
}
createObservable.$inject = promiseFn.$inject;
return createObservable;
}
function extractPromiseFn(promiseMaker) {
if (angular.isFunction(promiseMaker)) {
return promiseMaker;
}
if (angular.isArray(promiseMaker)) {
var promiseFn = promiseMaker[promiseMaker.length - 1];
promiseFn.$inject = promiseMaker.slice(0, promiseMaker.length - 1);
return promiseFn;
}
}
#2
2
Based on sielakos answer using an special service i came up with this solution. First, i need a additional service which keeps a reference of the data from the resovle.
基于sielakos回答使用特殊服务,我提出了这个解决方案。首先,我需要一个额外的服务,以保持resovle数据的参考。
Service
.service('dataLink', function () {
var storage = null;
function setData(data) {
storage = data;
}
function getData() {
return storage;
}
return {
setData: setData,
getData: getData
};
})
Well, i have to use the service in my resolve function like so
好吧,我必须在我的解析功能中使用该服务
Resolve function
resolve: {
detailResolver: function($http, $stateParams, dataLink) {
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
dataLink.setData(response.data);
return response.data;
});
}
}
Notice the line dataLink.setData(response.data);
. It keeps the data from the resolve in the service so I can access it from within the controller.
注意行dataLink.setData(response.data);.它保留了服务中的解析数据,因此我可以从控制器中访问它。
Controller
I modified the controller a little. I wrapped all the initialisation suff in an function i can execute when the data changes. The second thing is to watch the return value of the dataLink.getData();
我稍微修改了控制器。我将所有初始化后缀包装在我可以在数据更改时执行的函数中。第二件事是观察dataLink.getData()的返回值;
As of https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $scope.$watch provides functionality to watch return values of functions.
从https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $ scope开始。$ watch提供了观察函数返回值的功能。
Here is some Q&D example:
这是一些Q&D示例:
.controller('childCtrl', function($scope, $log, detailResolver, $interval, dataLink) {
initialise();
/*
* some stuff happens here
*/
$interval(function() {
console.log(detailResolver.id)
}, 1000);
$scope.$watch(dataLink.getData, function(newData) {
detailResolver = newData;
initialise();
});
function initialise() {
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.id = detailResolver;
}
})
The line $scope.$watch(dataLink.getData, function(newData) { ... });
does the trick. Every time the data in the dataLink service changes the callback kicks in and replaces the old data with the new one. Ive created a plunker so you can give it a try https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM
行$ scope。$ watch(dataLink.getData,function(newData){...});诀窍。每次dataLink服务中的数据发生更改时,回调都会启动,并用新数据替换旧数据。我创建了一个plunker所以你可以尝试一下https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM
You don't have to be afraid of memory leaks using this solution cause angular is removing watchers automatically. See https://*.com/a/25114028/6460149 for more information.
使用此解决方案时,您不必担心内存泄漏,因为angular会自动删除观察者。有关更多信息,请参阅https://*.com/a/25114028/6460149。
#3
1
1) For current task ng-view is not needed (IMHO). If you need two different scopes then redesign ng-views to become directives with their own controllers. This will prevent angular to reload them
1)对于当前任务,不需要ng-view(恕我直言)。如果您需要两个不同的范围,则重新设计ng-views以使用自己的控制器成为指令。这将防止角度重新加载它们
2) if you need to share data between scopes then service could be used to store data (see helperService in the following code)
2)如果需要在作用域之间共享数据,则可以使用服务来存储数据(请参阅以下代码中的helperService)
3) if we talk about current code simplification then it could be done so: use service from 2) and just use one controller:
3)如果我们谈论当前的代码简化,那么它可以这样做:使用来自2)的服务并且只使用一个控制器:
(function() {
angular.module('app',[
'ui.router'
]);
})();
(function() {
angular
.module('app')
.service('helperService', helperService);
helperService.$inject = ['$http', '$log'];
function helperService($http, $log) {
var vm = this;
$log.info('helperService');
vm.data = {
id: 0
};
vm.id = 0;
vm.loadData = loadData;
function loadData(id) {
vm.id = id;
$http
.get('file'+id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
vm.data = response.data;
});
}
}
})();
(function() {
angular
.module('app')
.controller('AppController', ParentController);
ParentController.$inject = ['helperService', '$log'];
function ParentController(helperService, $log) {
var vm = this;
$log.info('AppController');
vm.helper = helperService;
}
})();
4) interval, watch, broadcast, etc are not needed as well
4)不需要间隔,观看,广播等
Full code is here: plunker
完整代码在这里:plunker
P.S. don't forget about angularjs-best-practices/style-guide
附:不要忘记angularjs-best-practices / style-guide
#1
1
Not so pretty, but working solution would be to use events. Well, maybe it is not that bad, at least it is not complicated. https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview
不太好,但工作解决方案是使用事件。好吧,也许它不是那么糟糕,至少它并不复杂。 https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview
angular.module('app',[
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views:{
'parent':{
controller: 'parentCtrl',
template: '<div id="parent">'+
'<button ng-click="go(1)">1</button><br>'+
'<button ng-click="go(2)">2</button><br>'+
'<button ng-click="go(3)">3</button><br>'+
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views:{
'child@':{
controller: 'childCtrl',
template:'<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: function($http, $stateParams, $rootScope) {
return $http.get('file'+$stateParams.id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
$rootScope.$broadcast('newData', response.data);
return response.data;
});
}
}
});
})
.controller('parentCtrl', function ($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function (id) {
$state.go('parent.child', {id: id}, {notify:notify});
notify = false;
};
})
.controller('childCtrl', function ($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.$on('newData', function (event, detailResolver) {
$scope.id = detailResolver;
});
$scope.id = detailResolver;
$interval(function(){
console.log(detailResolver.id)
},1000)
})
;
EDIT: A little bit more complicated solution, that requires changing promise creator function into observables, but works: https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p=preview
编辑:一个更复杂的解决方案,需要将promise创建者函数更改为observables,但有效:https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p = preview
angular.module('app', [
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views: {
'parent': {
controller: 'parentCtrl',
template: '<div id="parent">' +
'<button ng-click="go(1)">1</button><br>' +
'<button ng-click="go(2)">2</button><br>' +
'<button ng-click="go(3)">3</button><br>' +
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views: {
'child@': {
controller: 'childCtrl',
template: '<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: turnToObservable(['$http', '$stateParams', function($http, $stateParams) { //Have to be decorated either be this or $inject
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
return response.data;
});
}])
}
});
})
.controller('parentCtrl', function($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function(id) {
$state.go('parent.child', {id: id}, {notify: notify});
notify = false;
};
})
.controller('childCtrl', function($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
detailResolver.addListener(function (id) {
$scope.id = id;
});
});
function turnToObservable(promiseMaker) {
var promiseFn = extractPromiseFn(promiseMaker);
var listeners = [];
function addListener(listener) {
listeners.push(listener);
return function() {
listeners = listeners.filter(function(other) {
other !== listener;
});
}
}
function fireListeners(result) {
listeners.forEach(function(listener) {
listener(result);
});
}
function createObservable() {
promiseFn.apply(null, arguments).then(fireListeners);
return {
addListener: addListener
};
}
createObservable.$inject = promiseFn.$inject;
return createObservable;
}
function extractPromiseFn(promiseMaker) {
if (angular.isFunction(promiseMaker)) {
return promiseMaker;
}
if (angular.isArray(promiseMaker)) {
var promiseFn = promiseMaker[promiseMaker.length - 1];
promiseFn.$inject = promiseMaker.slice(0, promiseMaker.length - 1);
return promiseFn;
}
}
#2
2
Based on sielakos answer using an special service i came up with this solution. First, i need a additional service which keeps a reference of the data from the resovle.
基于sielakos回答使用特殊服务,我提出了这个解决方案。首先,我需要一个额外的服务,以保持resovle数据的参考。
Service
.service('dataLink', function () {
var storage = null;
function setData(data) {
storage = data;
}
function getData() {
return storage;
}
return {
setData: setData,
getData: getData
};
})
Well, i have to use the service in my resolve function like so
好吧,我必须在我的解析功能中使用该服务
Resolve function
resolve: {
detailResolver: function($http, $stateParams, dataLink) {
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
dataLink.setData(response.data);
return response.data;
});
}
}
Notice the line dataLink.setData(response.data);
. It keeps the data from the resolve in the service so I can access it from within the controller.
注意行dataLink.setData(response.data);.它保留了服务中的解析数据,因此我可以从控制器中访问它。
Controller
I modified the controller a little. I wrapped all the initialisation suff in an function i can execute when the data changes. The second thing is to watch the return value of the dataLink.getData();
我稍微修改了控制器。我将所有初始化后缀包装在我可以在数据更改时执行的函数中。第二件事是观察dataLink.getData()的返回值;
As of https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $scope.$watch provides functionality to watch return values of functions.
从https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $ scope开始。$ watch提供了观察函数返回值的功能。
Here is some Q&D example:
这是一些Q&D示例:
.controller('childCtrl', function($scope, $log, detailResolver, $interval, dataLink) {
initialise();
/*
* some stuff happens here
*/
$interval(function() {
console.log(detailResolver.id)
}, 1000);
$scope.$watch(dataLink.getData, function(newData) {
detailResolver = newData;
initialise();
});
function initialise() {
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.id = detailResolver;
}
})
The line $scope.$watch(dataLink.getData, function(newData) { ... });
does the trick. Every time the data in the dataLink service changes the callback kicks in and replaces the old data with the new one. Ive created a plunker so you can give it a try https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM
行$ scope。$ watch(dataLink.getData,function(newData){...});诀窍。每次dataLink服务中的数据发生更改时,回调都会启动,并用新数据替换旧数据。我创建了一个plunker所以你可以尝试一下https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM
You don't have to be afraid of memory leaks using this solution cause angular is removing watchers automatically. See https://*.com/a/25114028/6460149 for more information.
使用此解决方案时,您不必担心内存泄漏,因为angular会自动删除观察者。有关更多信息,请参阅https://*.com/a/25114028/6460149。
#3
1
1) For current task ng-view is not needed (IMHO). If you need two different scopes then redesign ng-views to become directives with their own controllers. This will prevent angular to reload them
1)对于当前任务,不需要ng-view(恕我直言)。如果您需要两个不同的范围,则重新设计ng-views以使用自己的控制器成为指令。这将防止角度重新加载它们
2) if you need to share data between scopes then service could be used to store data (see helperService in the following code)
2)如果需要在作用域之间共享数据,则可以使用服务来存储数据(请参阅以下代码中的helperService)
3) if we talk about current code simplification then it could be done so: use service from 2) and just use one controller:
3)如果我们谈论当前的代码简化,那么它可以这样做:使用来自2)的服务并且只使用一个控制器:
(function() {
angular.module('app',[
'ui.router'
]);
})();
(function() {
angular
.module('app')
.service('helperService', helperService);
helperService.$inject = ['$http', '$log'];
function helperService($http, $log) {
var vm = this;
$log.info('helperService');
vm.data = {
id: 0
};
vm.id = 0;
vm.loadData = loadData;
function loadData(id) {
vm.id = id;
$http
.get('file'+id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
vm.data = response.data;
});
}
}
})();
(function() {
angular
.module('app')
.controller('AppController', ParentController);
ParentController.$inject = ['helperService', '$log'];
function ParentController(helperService, $log) {
var vm = this;
$log.info('AppController');
vm.helper = helperService;
}
})();
4) interval, watch, broadcast, etc are not needed as well
4)不需要间隔,观看,广播等
Full code is here: plunker
完整代码在这里:plunker
P.S. don't forget about angularjs-best-practices/style-guide
附:不要忘记angularjs-best-practices / style-guide