Angular ui-router - how to assign resolved data to state data - angularjs

How do I assign resolved data into state data w/o going in the controller code?
following code fails
.state('restaurant', {
abstract: true,
url: '/:restaurantId/:restaurantName',
resolve: {
restaurant: ['RestaurantFactory', '$stateParams', '$state', function (RestaurantFactory, $stateParams, $state) {
return RestaurantFactory.query({ restaurantId: $stateParams.restaurantId });
}]
},
data: {
restaurant: resolve.restaurant // fails
},
views: {
'content#': {
templateUrl: 'app/modules/restaurant/restaurant.html',
controllerAs: 'RestCtrl',
controller: ['restaurant', 'RestaurantFactory', 'AppLib', 'Page', '$scope', '$state', function (restaurant, RestaurantFactory, AppLib, Page, $scope, $state) {
var vm = this;
// public properties
vm.AppLib = AppLib;
vm.Page = Page;
vm.restaurant = restaurant;
$state.restaurant = restaurant;
$scope.$on('Restaurant_Changed', function (event, data) { if (data.id === vm.restaurant.id) { vm.restaurant = data; $state.restaurant = data; } });
}]
}
}
})
The reason I need this functionality is that one of the child state overwrites the content# view (kind of brings entire content of its own) and as a result parent's controller is never fired if I access child states page directly. Is there a way to accomplish this as I want to use parent state URL and ability to share data down to children/grandchildren states
Any ideas?
Solution
Big thanks to Yang Li for giving me this suggestion. Here is how I solved it
resolve: {
restaurant: ['RestaurantFactory', '$stateParams', '$state', '$q', function (RestaurantFactory, $stateParams, $state, $q) {
var deferred = $q.defer();
RestaurantFactory.query({ restaurantId: $stateParams.restaurantId })
.then(function (data) {
$state.restaurant = data;
deferred.resolve($state.restaurant);
})
.then(function (data) {
deferred.reject(data);
})
return deferred.promise;
}],
},

Related

UI Resolve is not injecting into controller (data is undefined)

I've looked at similar questions but I can't seem to understand what I am missing. Basically, I have a service that gets data from the server, and I am trying to get that data into a controller through UI-Router's resolve property. However, after following numerous tutorials and documentations, I can't get the controller to find the data, so to speak. Everything comes up as undefined. I am hoping someone can help me understand what is happening. My code is below.
services.js
myServices.factory('SoundCloudService', ['$http', '$log', '$sce', function($http, $log, $sce) {
function getPlayerHtml() {
return $http.get('/get-site-data').then(function(oEmbed) {
return $sce.trustAsHtml(oEmbed.data.player);
});
};
function getSiteAbout() {
return $http.get('/get-site-data').then(function(oEmbed) {
return $sce.trustAsHtml(oEmbed.data.about);
});
}
function getAllTracks() {
return $http.get('/get-all-tracks').then(function(tracks) {
return JSON.parse(tracks.data);
});
};
function getAllPlaylists() {
return $http.get('/get-playlists').then(function(playlists) {
return JSON.parse(playlists.data);
})
};
function getPlaylist(pid) {
return $http.post('/get-playlist', pid, $http.defaults.headers.post).then(function(playlist) {
return playlist.data;
});
};
function getXMostTrendingFrom(x, playlist) {
var i, trending = [];
playlist.sort(function(a, b) { return b.playback_count - a.playback_count} );
for(i=0;i<x;i++) {
trending.push(all_tracks[i]);
}
return trending;
};
return {
getAllTracks: getAllTracks,
getAllPlaylists: getAllPlaylists,
getPlayerHtml: getPlayerHtml,
getSiteAbout: getSiteAbout,
getXMostTrendingFrom: getXMostTrendingFrom,
getPlaylist: getPlaylist,
};
}]);
app.js
myApp.config(['$stateProvider', '$urlRouterProvider', 'ngMetaProvider',
function($stateProvider, $urlRouterProvider, ngMetaProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('main', {
url: '',
template: '<ui-view/>',
abstract:true,
controller: 'MainController',
resolve: {
player: function(SoundCloudService) { return SoundCloudService.getPlayerHtml(); },
about: function(SoundCloudService) { return SoundCloudService.getSiteAbout(); },
}
})
.state('main.home', {
url: '/',
templateUrl: '../static/partials/home.html',
controller: 'IndexController',
})
.state('main.team', {
url: '/team',
templateUrl: '../static/partials/team.html',
controller: 'TeamController',
})
.state('main.contact', {
url: '/contact',
templateUrl: '../static/partials/contact.html',
controller: 'ContactController',
})
.state('main.resources', {
url: '/resources',
templateUrl: '../static/partials/resources.html',
controller: 'ResourcesController',
})
.state('main.listen-to', {
url: '/listen-to',
templateUrl: '../static/partials/listen-to.html',
controller: 'ListenController',
})
.state('main.listen-to.season', {
url: '/listen-to/:season',
templateUrl: '../static/partials/listen-to.season.html',
controller: 'ListenController',
})
.state('main.listen-to.season.episode', {
url: '/listen-to/:season/:episode',
templateUrl: '../static/partials/listen-to.season.episode.html',
controller: 'ListenController',
})
.state('main.read', {
url: '/read',
templateUrl: '../static/partials/read.html',
controller: 'ReadController',
})
.state('main.read.post', {
url: '/read/:post',
templateUrl: '../static/partials/read.post.html',
controller: 'ReadController',
})
}
]);
controller.js
myControllers.controller('MainController', ['$scope', '$log', 'PageTitleService',
function($scope, $log, PageTitleService, player) {
$log.log(player); /* This is always undefined */
}
]);
[UPDATE]
As pointed out by Hadi in the answer below, I placed player in the array, and the controller now looks like this:
skodenControllers.controller('MainController', ['$scope', '$log', '$sce', 'PageTitleService', 'player',
function($scope, $log, $sce, PageTitleService, player) {
$log.log(player);
}
]);
The console DOES show the data, but only after an error as such:
Error: [$injector:unpr]
http://errors.angularjs.org/1.3.2/$injector/unpr?p0=playerProvider%20%3C-%20player
at angular.js:38
at angular.js:3930
at Object.d [as get] (angular.js:4077)
at angular.js:3935
at d (angular.js:4077)
at Object.e [as invoke] (angular.js:4109)
at F.instance (angular.js:8356)
at angular.js:7608
at r (angular.js:347)
at I (angular.js:7607)
Hopefully someone can lead me in the right direction.
You forgot pass player into array. change to this
myControllers.controller('MainController', ['$scope', '$log',
'PageTitleService','player',
function($scope, $log, PageTitleService, player) {
$log.log(player); /* This is always undefined */
}
]);
As myServices and myControllers are both modules, ensure you add them as dependencies of myApp module.
// init myApp module
angular.module('myApp', ['myServices', 'myControllers']);
Edit
Some leads :
According to the documentation, when using ui-router nested views, child views (state name = main.xxx) must declare the parent state, so you must add parent: "main" or child views won't inherit resolved properties of main state controller
As siteDate is loaded asynchronously in SoundCloudService (services.js:23), you cannot be sure it will be available in your controllers which are loaded at the same time.
Instead, add a getSiteDate() method to SoundCloudService which returns a promise. siteData is then cached and immediately return by the promise.
For example :
/**
* #name getSiteData
* #description Scrap site data
* #returns {promise} a promise
*/
function getSiteData() {
var deferred = $q.defer();
if(siteData) {
deferred.resolve(siteData);
}
else {
$http.get('/get-site-data').then(function(response) {
siteData = response.data;
deferred.resolve(siteData);
}, function(err) {
deferred.reject(err.message);
});
}
return deferred.promise;
}
Why trying to map SoundCloudService to siteData ? You should simply inject SoundCloudService in controllers that use it :
For example :
skodenControllers.controller('MainController', ['$scope', '$log', '$sce', 'PageTitleService', 'SoundCloudService',
function($scope, $log, $sce, PageTitleService, SoundCloudService) {
// Note: getSiteData() could use a cache inside the service
SoundCloudService.getSiteData().then(function(siteData) {
...
});
}

how to update angular ui.router nested state immediately?

I tried to make modal popup with angularjs, webpack2.
But some problems are bothering me..
I want to make modal popup that has custom url but
in modal popup, $state.current still indicates parent's state.
I searched onEnter function is invoked when state transition is done completely.
and meantime, modal's state is not updated. (I think this is the reason why modal's state indicates parent's state)
I can get modal's own state and stateParams with setTimeout function but it is just trick right ?
How can I get modal's state with the right way?
===
this is my app.js,
// HOME
.state('home', {
url: '/',
templateUrl: 'client/pages/home/home.html',
controller: 'HomeController',
})
// PLAYER MODAL
.state('player', {
url: 'player/:playerName',
parent: 'home',
onEnter: function($stateParams, $state, $uibModal) {
$uibModal.open({
templateUrl: 'client/pages/player/player.html',
resolve: {},
controller: 'PlayerController'
}).result.then(function (result) {
// $scope.$close
alert('result ->' + result);
}, function (result) {
// $scope.$dismiss
alert('dismiss ->' + result);
}).finally(function () {
// handle finally
return $state.go('home');
});
}
});
HomeController,
angular.module('907degree')
.controller('HomeController', function ($scope, $state) {
$scope.state = $state.current;
$scope.players = ['messi', 'hazard', 'ronaldo'];
$scope.showModal = function (playerId) {
$state.go('player', { playerName: playerId });
};
})
and PlayerController.
angular.module('907degree')
.controller('PlayerController', ($scope, $http, $state, $stateParams, itemService) => {
$scope.playerState = $state.current;
$scope.params = $stateParams.playerName;
$scope.item = itemService.get($stateParams.id);
$scope.ok = function () {
$scope.$close('clicked OK');
};
$scope.dismiss = function () {
$scope.$dismiss('clicked CANCEL');
};
$scope.checkState = function () {
console.log($state.current, $stateParams);
console.log($scope.playerName);
}
})
I think my repo is better than plunker to solve this problem.. so here is it.
git clone hagfish1210#gitlab.com/hagfish1210/907degrees.git & npm install & npm run dev
Inject the current $transition$.
onEnter: function($transition$, $state, $uibModal) {
$uibModal.open({
templateUrl: 'client/pages/player/player.html',
resolve: { $transition$: () => $transition$ },
controller: 'PlayerController'
}).result.then(function (result) {
// $scope.$close
alert('result ->' + result);
}, function (result) {
// $scope.$dismiss
alert('dismiss ->' + result);
}).finally(function () {
// handle finally
return $state.go('home');
});
}
In PlayerController ask the transition for its parameters
angular.module('907degree')
.controller('PlayerController', ($scope, $http, $state, $transition$, itemService) => {
$scope.playerState = $state.current;
var params = $transition$.params();
$scope.params = params.playerName;
$scope.item = itemService.get(params.id);
$scope.ok = function () {
$scope.$close('clicked OK');
};
$scope.dismiss = function () {
$scope.$dismiss('clicked CANCEL');
};
$scope.checkState = function () {
console.log($state.current, params);
console.log($scope.playerName);
}
})
The Transition class exposes much information about the running transition: https://ui-router.github.io/ng1/docs/latest/classes/transition.transition-1.html#params

AngularJS UI router: Block view

Right now i am making an AngularJS+UI router install application. But i have a problem, the problem is, that i want to disable access to the views, associated with the install application. I want to do it in resolve in the state config.
But the problem is i need to get the data from a RESTful API, whether the application is installed or not. I tried making the function, but it loaded the state before the $http.get request was finished.
Here was my code for the resolve function:
(function() {
var app = angular.module('states', []);
app.run(['$rootScope', '$http', function($rootScope, $http) {
$rootScope.$on('$stateChangeStart', function() {
$http.get('/api/v1/getSetupStatus').success(function(res) {
$rootScope.setupdb = res.db_setup;
$rootScope.setupuser = res.user_setup;
});
});
}]);
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/404");
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
data: function($q, $state, $timeout, $rootScope) {
var setupStatus = $rootScope.setupdb;
var deferred = $q.defer();
$timeout(function() {
if (setupStatus === true) {
$state.go('setup-done');
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}
}
})
.state('user-registration', {
url: "/install/user-registration",
templateUrl: "admin/js/partials/user-registration.html",
controller: "RegisterController"
})
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html"
})
.state('404', {
url: "/404",
templateUrl: "admin/js/partials/404.html"
});
}]);
})();
EDIT:
Here is what my ajax call returns:
Try this way:
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
setupStatus: function($q, $state, $http) {
return $http.get('/api/v1/getSetupStatus').then(function(res) {
if (res.db_setup === true) {
$state.go('setup-done');
return $q.reject();
}
return res;
});
}
}
})
Then inject setupStatus in controller:
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html",
controller: ['$scope', 'setupStatus', function ($scope, setupStatus) {
$scope.setupdb = setupStatus.db_setup;
$scope.setupuser = setupStatus.user_setup;
}]
})

unable to open a modal with angular and ui.routes

I am trying to follow this example to show a bootstrap modal on a certain state. It works fine without a modal (so the state config should be ok). All needed dependencies (ie angular bootstrap) should be available.
when I do a console.debug($stateParams) before $modal.open I get the correct data, within the $modal.open-method however the stateParams from the last state are returned (the state I am coming from)
Any hints?
EDIT
the relevant state cfg:
.state('publications.view', {
parent: 'publications.productSelection',
url: '/{productSlug:[a-zA-Z0-9-]+}/{docID:[0-9]+}_{slug:[a-zA-Z0-9-]+}',
onEnter: ['restFactory', '$state', '$stateParams', '$modal',
function(restFactory, $state, $stateParams, $modal) {
console.debug($stateParams.docID);
$modal.open({
templateUrl: 'partials/publication.html',
resolve: {
publication: ['restFactory', '$stateParams',
function(restFactory, $stateParams) {
console.debug($state.params);
console.debug($stateParams);
return restFactory.view($stateParams.language, $stateParams.productSlug, $stateParams.docID);
}
]
},
controller: ['$scope', '$sce', 'publication', '$rootScope',
function($scope, $sce, publication, $rootScope) {
$rootScope.pageTitle = publication.data.data.publication.Publication.title;
$scope.publication = $sce.trustAsHtml(publication.data.data.publication.Publication.content);
}
]
});
}
]
});
You can get around this issue by injecting the current $stateParams into the onEnter function, save them as state in some service, and inject that service instead into your modal resolves.
I am adapting the code from here: Using ui-router with Bootstrap-ui modal
.provider('modalState', function($stateProvider) {
var modalState = {
stateParams: {},
};
this.$get = function() {
return modalState;
};
this.state = function(stateName, options) {
var modalInstance;
$stateProvider.state(stateName, {
url: options.url,
onEnter: function($modal, $state, $stateParams) {
modalState.stateParams = $stateParams;
modalInstance = $modal.open(options);
modalInstance.result['finally'](function() {
modalInstance = null;
if ($state.$current.name === stateName) {
$state.go('^');
}
});
},
onExit: function() {
if (modalInstance) {
modalInstance.close();
}
}
});
};
})
Then in your app config section
.config(function($stateProvider, $urlRouterProvider, modalStateProvider) {
modalStateProvider.state('parent.child', {
url: '/{id:[0-9]+}',
templateUrl: 'views/child.html',
controller: 'ChildCtrl',
resolve: {
role: function(Resource, modalState) {
return Resource.get({id: modalState.stateParams.id}).$promise.then(function(data) {
return data;
});
}
}
});
}

Loading data for controller in Angular.js with resolve and $resource

I'm trying to load a job by Id from the API and pass it to the controller.
.when('/jobs/edit/:id', {
templateUrl: 'partials/jobs/edit',
controller: 'JobCtrl',
resolve: function($routeParams, Job){
var jobId = $routeParams.id;
return {
job: function(){
return Job.get({ id: jobId});
}
};
}
})
Controller:
angular.module('App')
.controller('JobCtrl', function ($scope, Job, $location, $routeParams) {
$scope.newJob = data.job; //does not work
$scope.errors = {};
$scope.save = function (form) {
//...
};
});
Model:
angular.module('App')
.factory('Job', function ($resource) {
return $resource('/api/jobs/:id', {
id: '#id'
}, { //parameters default
update: {
method: 'PUT'
}
});
});
How do I get the data in the controller? My resolve block in the route is not even being executed.
Edit: this page has lots of examples of different types of resolving:
http://phillippuleo.com/articles/angularjs-timing-multiple-resource-resolves-ngroute-and-ui-router
It seems like you resolve implementation is incorrect.
You could try to change router like:
.when('/jobs/edit/:id', {
templateUrl: 'partials/jobs/edit',
controller: 'JobCtrl',
resolve: {
job: function (Job, $routeParams) {
return Job.get({id: $routeParams.id});
}
}
})
And get resoled data in controller by including Job into dependencies:
angular.module('App')
.controller('JobCtrl', function ($scope, job, $location, $routeParams) {
// job is resolved here
$scope.job = job;
$scope.newJob = {};
$scope.errors = {};
$scope.save = function (form) {
//...
};
});
UPDATED
If you want to use JobCtrl either for creating and editing, you can return null in resoled job for new-job-page. It means: There aren't any job yet, till you create one.
.when('/jobs/new', {
templateUrl: 'partials/jobs/new',
controller: 'JobCtrl',
resolve: {
job: function () {
return null;
}
}
})

Resources