I am using the AngularJS framework (as well as its ui-router), and I am having a hard time getting my data to resolve the way I want. I will give an example below of what I am trying to do:
$stateProvider
.state('home',{
views: {
'test': {
templateUrl: 'test.html',
resolve: {
config: function() {
var result = /*service call that returns json*/
return result;
}
}
controller: function($scope, config){
console.log(config);
}
},
'test': {
templateUrl: 'test.html',
resolve: {
config: function() {
var result = /*service call that returns DIFFERENT json*/
return result;
}
}
controller: function($scope, config){
console.log(config);
}
}
}
})
<div ui-view="test">
<div ui-view="test">
Is there any way in which to uniqely assign and inject 'config' into the controller so that it contains the json that its respective service call returned? The idea here is I want the same template but with different configs coming into them (scoped to that particular view). Right now, config just contains the last resolve that was executed.
I hope I am not confusing. Let me know if you need any clarifications...Much appreciated!
The views need to be named differently and so do the resolves. Using the same template for both of them is not an issue. Lastly, the resolve has to be relative to the state, not the views.
$stateProvider
.state('home',{
views: {
'test': {
templateUrl: 'test.html',
controller: function($scope, config){
console.log(config);
}
},
'test2': {
templateUrl: 'test.html',
controller: function($scope, config2){
console.log(config2);
}
}
},
resolve: {
config: function() {
var result = /*service call that returns json*/
return result;
},
config2: function() {
var result = /*service call that returns DIFFERENT json*/
return result;
}
}
})
<div ui-view="test">
<div ui-view="test2">
Related
I have set up a service to return a listing of clients from my API. Using UI-router, I can successfully pass a client's id to the details state - however, it seems unnecessary here to make another API call to retrieve a single client when I have all the necessary data in my controller.
What is the best way to use the ID in my detail state URL to show data for that client? Also - if a user browses directly to a client detail URL - I'll need to then make a call to the API to get just that client data - or is there a better way?
EDIT: I am not looking to load the two views on the same 'page', but completely switch views here, from a listing page to a detail page.
Routes in App.js
$stateProvider
.state('root', {
abstract: true,
url: '',
views: {
'#': {
templateUrl: '../partials/icp_index.html',
controller: 'AppController as AppCtrl'
},
'left-nav#root': {
templateUrl: '../partials/left-nav.html'
},
'right-nav#root': {
templateUrl: '../partials/right-nav.html'
},
'top-toolbar#root': {
templateUrl: '../partials/toolbar.html'
}
/*'footer': {
templateUrl: '../partials/agency-dashboard.html',
controller: 'AppController as AppCtrl'
}*/
}
})
.state('root.clients', {
url: '/clients',
views: {
'content#root': {
templateUrl: '../partials/clients-index.html',
controller: 'ClientsController as ClientsCtrl'
}
}
})
.state('root.clients.detail', {
url: '/:clientId',
views: {
'content#root': {
templateUrl: '../partials/client-dashboard.html',
//controller: 'ClientsController as ClientsCtrl'
}
}
})
// ...other routes
Service, also in app.js
.service('ClientsService', function($http, $q) {
this.index = function() {
var deferred = $q.defer();
$http.get('http://api.icp.sic.com/clients')
.then(function successCallback(response) {
console.log(response.data);
deferred.resolve(response.data);
},
function errorCallback(response) {
// will handle error here
});
return deferred.promise;
}
})
And my controller code in ClientsController.js
.controller('ClientsController', function(ClientsService) {
var vm = this;
ClientsService.index().then(function(clients) {
vm.clients = clients.data;
});
});
And finally, my listing page clients-index.html
<md-list-item ng-repeat="client in ClientsCtrl.clients" ui-sref="clients-detail({clientId : client.id })">
<div class="list-item-with-md-menu" layout-gt-xs="row">
<div flex="100" flex-gt-xs="66">
<p ng-bind="client.name"></p>
</div>
<div hide-xs flex="100" flex-gt-xs="33">
<p ng-bind="client.account_manager"></p>
</div>
</div>
</md-list-item>
You can use inherited states like suggested here.
$stateProvider
// States
.state("main", {
controller:'mainController',
url:"/main",
templateUrl: "main_init.html"
})
.state("main.details", {
controller:'detailController',
parent: 'main',
url:"/:id",
templateUrl: 'form_details.html'
})
Your service does not change.
Your controllers check if the Model has been retrieved:
app.controller('mainController', function ($scope, ClientsService) {
var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
promise.then(function(data){
$scope.Model = data;
});
})
app.controller('detailController', function ($q, $scope, ClientsService, $stateParams) {
var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
promise.then(function(data){
$scope.Model = data;
$scope.Item = data[$stateParams.id];
});
})
See
http://plnkr.co/edit/I4YMopuTat3ggiqCoWbN?p=preview
[UPDATE]
You can also, if you must, combine both controllers:
app.controller('mainController', function ($q, $scope, ClientsService, $stateParams) {
var promise = $scope.Model ? $q.when($scope.Model) : ClientsService.index();
promise.then(function(data){
$scope.Model = data;
$scope.Item = data[$stateParams.id];
});
})
I would change the service to cache the data. With $q.when() you can return a promise from a variable. So you save your response in a variable, and before doing the API call you check if the cache has been set. If there is any cache, you return the data itself. Otherwise, you do the usual promise call.
.service('ClientsService', function($http, $q) {
var clients = null;
this.getClient = function(id) {
if (clients !== null) {
return $q.when(id ? clients[id] : clients);
}
var deferred = $q.defer();
$http.get('http://api.icp.sic.com/clients').then(function(response) {
clients = response.data;
deferred.resolve(id ? clients[id] : clients);
}, function (response) {
// will handle error here
});
return deferred.promise;
}
})
I am using UI-Router and want to change routing to be 'component based'. So Instead of defining a controller / template I want to use it like this:
.state('issue', {
url: '/someUrl/:number',
template: '<my-directive></my-directive>',
resolve: {
data: function(dataService) {
return dataService.getData().then(function(response) {
console.log(response.data);
return response.data;
});
}
}
})
Now, I know that with Angular's ngRoute I can use resolved data directly in the template, for example:
$routeProvider
.when('/', {
template: `<my-directive data="resolve.data"></my-directive>`,
resolve: {
data: function (dataService) {
return dataService.getData();
}
}
})
I couldn't do it using UI-Router (value was undefined).
Am I doing something wrong? Is it possible using ui-router?
The point with UI-Router is - result of resolve is available for a controller (related to template). So, we could do it like this:
.state('issue', {
url: '/someUrl/:number',
template: '<my-directive data="stateCtrlData"></my-directive>',
controller: function($scope, data) { $scope.stateCtrlData = data },
resolve: {
data: function(dataService) {
return dataService.getData().then(function(response) {
console.log(response.data);
return response.data;
});
}
}
})
The data are passed into controller and from its scope we pass it to directive.
If you mean injecting your data service, then you can do it like this (remember that the '' is telling to inject):
state('issue', {
url: '/someUrl/',
template: '<my-directive data="pep.data"></my-directive>',
controller: function( data) { this.data = data },
controllerAs: 'pep',
resolve:{
dataSvc : 'YourDataSvc',
campaign : function(dataSvc){
return dataSvc.getData();
}
}
Please remember a ui-view will be expected if you want to put additional views or child states.
Actually you can (tested only in ui-router v0.3.2)
There's an undocumented $resolve variable which is automatically injected in the controller.
Simply add 'controllerAs' property to the state as follows, and you can use $resolve in the template:
.state('issue', {
url: '/someUrl/:number',
template: '<my-directive data="vm.$resolve.data"></my-directive>',
controllerAs: 'vm',
resolve: {
data: function(dataService) {
return dataService.getData().then(function(response) {
console.log(response.data);
return response.data;
});
}
}
})
So here's the relevant segment from my router:
app.js
.state('app.browse', {
url: "/browse/:question",
controller: 'QCtrl',
resolve: {
question: function($stateParams, qService){
console.log(qService.getQuestion($stateParams.question).q);
return qService.getQuestion($stateParams.question).q;
}
},
views: {
'menuContent': {
templateUrl: "templates/browse.html"
}
}
})
/* QSERVICE HERE */
.factory('qService', function() {
var questions = [ {'q': "text text"} ];
return {
questions: questions,
getQuestion: function(index) {
return questions[index]
}
}
})
controllers.js
.controller('QCtrl', function($scope, question){
$scope.questions = qService.questions;
$scope.question = question;
})
Which finds exactly what I'm looking for as demonstrated by the console log.
However in my browser view, I am unable to grab the question variable!
browser.html
<ion-view view-title="Browse">
{{question}}
</ion-content>
Which always displays as empty! Why is this happening, and how do I resolve it?
Resolve will not bind question unto your controller.
In your controller do this
.controller('QCtrl', function ($scope, question) {
$scope.question = question;
})
In addition - in your state object, the question is being passed incorrectly. Correction:
.state('app.browse', {
url: "/browse/:question",
resolve: {
question: function($stateParams, qService){
return qService.getQuestion($stateParams.question);
}
},
views: {
'menuContent': {
templateUrl: "templates/browse.html",
controller: 'QCtrl',
}
}
})
You're also missing templateUrl in your state object. Update this to reflect where the the template is and it should be good to go :)
$stateProvider
.state('root', {
url: '',
views: {
'root.base': { templateUrl: '/templates/root/root.html' },
'root.sidebar': {
templateUrl: '/templates/root/root-sidebar.html',
controller: 'SomeDataController',
resolve: {
someData: function(DataService) { // DataService is an angular service for data retrieval
return DataService.getDataList(); // returns an array
}
},
}
}
});
If I attempt to run a resolve on a subview with angular-ui-router, the page simply comes up blank. If I omit the resolve, it loads fine -- but I need to resolve for some data before my controller is instantiated. Do I have my syntax correct? I've searched for examples high and low but can't seem to find any that match my situation.
Given my above example, shouldn't the "root-sidebar.html" have the scope of "SomeDataController", and shouldn't SomeDataController have the data resolved in the route definition declared above?
Your concept is working, maybe just some small settings are broken... I created working plunker
Here is the controller consuming someData
.controller('SomeDataController', function($scope, someData) {
$scope.data = someData
})
And here is our service loading some JSON and returning that as async:
.factory('DataService', ['$http',
function($http) {
return {
getDataList: function() {
return $http
.get("data.json")
.then(function(response) {
return response.data;
});
},
};
}
]);
The state defintion is unchanged (just adjusted to display the data):
$stateProvider
.state('root', {
url: '',
views: {
'root.base': {
template: '<div>root base view</div>',
},
'root.sidebar': {
template: '<div>root sidebar view' +
'<h5>the resolve data</h5>' +
'<pre>{{data | json}}</pre>' +
'</div>',
controller: 'SomeDataController',
resolve: {
someData: function(DataService) { // DataService is an angular service for data retrieval
return DataService.getDataList(); // returns an array
}
},
}
}
});
You can test that all here
When I change route, from say /set/1 to /set/2, then it still shows the information from /set/1 until I manually refresh the page, I've tried adding $route.refresh to the ng-click of the links to these pages, but that didn't work either. Any ideas?
Below is the routing code, this works fine, all routing is done via links, just <a> tags that href to the route.
angular.module('magicApp', ['ngRoute']).config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'pages/home.html'
}).when('/set', {
redirectTo: '/sets'
}).when('/set/:setID', {
controller: 'SetInformationController',
templateUrl: 'pages/set.html'
}).when('/card', {
redirectTo: '/cards'
}).when('/card/:cardID', {
controller: 'CardInformationController',
templateUrl: 'pages/card.html'
}).when('/sets', {
controller: 'SetListController',
templateUrl: 'pages/sets.html'
}).when('/cards', {
controller: 'CardListController',
templateUrl: 'pages/cards.html'
}).when('/search/:searchterm', {
controller: 'SearchController',
templateUrl: 'pages/search.html'
}).otherwise({
redirectTo: '/'
});
}]);
Below is the code for the SetListController, it uses the routeParams to grab the correct information from a service, which works, when I go to /set/1 then it returns the right information, if I then go back then go to /set/2 it still shows the information from set 1, until I refresh the page.
.controller('SetInformationController', function($scope, $routeParams, $route, SetInformationService, CardSetInformationService) {
$scope.set = [];
$scope.cards = [];
function init() {
SetInformationService.async($routeParams.setID).then(function(d) {
$scope.set = d;
});
CardSetInformationService.async($routeParams.setID).then(function(d) {
$scope.cards = d;
})
}
init();
})
The HTML itself has no reference to the controller, or anything like that, just the objects in the scope, namely set and cards.
I figured it out! The problem wasn't actually with the routing it was with my service, here was the service before:
.factory('SetInformationService', function($http) {
var promise;
var SetInformationService = {
async: function(id) {
if ( !promise ) {
// $http returns a promise, which has a then function, which also returns a promise
promise = $http.get('http://api.mtgdb.info/sets/' + id).then(function (response) {
// The then function here is an opportunity to modify the response
console.log("Set Information");
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
}
// Return the promise to the controller
return promise;
}
};
return SetInformationService;
})
where it should have been:
.factory('SetInformationService', function($http) {
var promise;
var SetInformationService = {
async: function(id) {
// $http returns a promise, which has a then function, which also returns a promise
promise = $http.get('http://api.mtgdb.info/sets/' + id).then(function (response) {
// The then function here is an opportunity to modify the response
console.log("Set Information");
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return SetInformationService;
})