What I'm trying to do is:
Make an api call as soon as the application runs (using ui-router and idea from http://www.jvandemo.com/how-to-resolve-angularjs-resources-with-ui-router/)
Store the data 'globally' so that my other controllers can have access to the data
Once the api call is done and all the controllers have resolved, show the UI (I don't want any flash of loaded content)
I know I could create an angular service to make those api calls within each controller, but if I have 2 controllers on the page, I don't want to make 2 of the same requests.
In the example below, I have a nav.controller.js that needs access to myData.
home.config.js
angular.module('myApp')
.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/',
templateProvider: function($templateCache) {
return $templateCache.get('home.client.view.html');
},
resolve: {
myService: 'myApiService',
myData: function(myApiService){
return myService.get().$promise;
}
},
controller: 'HomeCtrl as home'
});
}
]);
home.controller.js
angular.module('myApp')
.controller('HomeCtrl', ['myData',
function(myData){
var self = this;
// YAY WORKS!
console.log(myData);
}
]);
nav.controller.js
angular.module('myData')
.controller('NavCtrl', ['$scope', 'myData',
function($scope, myData) {
// BOO DOESN'T WORK
console.log(myData);
}
]);
another.controller.js
angular.module('anotherModule')
.controller('AnotherCtrl', ['$scope', 'myData',
function($scope, myData) {
// BOO DOESN'T WORK
console.log(myData);
}
]);
You should still use a service. However, this service shouldn't just make the API call - it should also store the data. Your code will look something like
home.controller.js
angular.module('myApp')
.controller('HomeCtrl', ['myService',
function(myService){
var self = this;
// YAY WORKS!
console.log(myService.myData);
}
]);
nav.controller.js
angular.module('myData')
.controller('NavCtrl', ['$scope', 'myService',
function($scope, myService) {
// BOO DOESN'T WORK
console.log(myService.myData);
}
]);
another.controller.js
angular.module('anotherModule')
.controller('AnotherCtrl', ['$scope', 'myService',
function($scope, myService) {
// BOO DOESN'T WORK
console.log(myService.myData);
}
]);
And inside your myService.get() function you store the returned data in myData
Related
edit: I'm using bootstrap, I think bootstrap tab is causing the
problem
View does not get updated after $scope variable update.
$scope.codeData
if i console the $scope.codeData, i can see the data, but does not render in view.
I have to click twice to get the view render correctly.
is there anything wrong with my code??
Thank you.
config
angular.module('SPAroutes', ['ngRoute', 'SPAcontrollers', 'SPAdirectives'])
.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider) {
$routeProvider
.when('/admin', {
templateUrl: 'templates/views/admin.html',
controller: 'adminCtrl',
controllerAs: 'admin'
})
$locationProvider.html5Mode(true).hashPrefix('!');
}]);
Controller.js
angular.module('SPAcontrollers', ['ngRoute', 'SPAfactories', 'SPAdirectives']).controller('adminCtrl', ['$scope', '$http', '$location', '$window', '$SPAaccount', function ($scope, $http, $location, $window, $SPAaccount) {
this.dataRetrive = function(category){
$http.get('/ctrls/get/blockCode/header').then(function (res){
$scope.codeData = res.data;
console.log($scope.codeData);
$('#headerTab').tab('show');
}, function (err){
console.log(err);
})
};
}]);
admin.html
{{codeData}}
You are mixing up controllerAs with scope as phil mentioned in his comment on question. Instead of using scope here store values insidethis reference something like this.
angular.module('SPAcontrollers', ['ngRoute', 'SPAfactories', 'SPAdirectives']).controller('adminCtrl', ['$scope', '$http', '$location', '$window', '$SPAaccount', function ($scope, $http, $location, $window, $SPAaccount) {
var admin = this;
this.dataRetrive = function(category){
$http.get('/ctrls/get/blockCode/header').then(function (res){
admin.codeData = res.data;
console.log(admin.codeData);
$('#headerTab').tab('show');
}, function (err){
console.log(err);
})
};
}]);
and inside the view: admin.html
{{admin.codeData}}
here is working plunk for your refernce
i saw a lot of similar questions at SO, but those solutions not worked for me :(
About my site and app:
backend is wordpress with json api, angular app is on the mysite.lc/catalog/ page.
my controller:
var catalogControllers = angular.module('catalogControllers', []);
catalogControllers.controller('catalogCtrl', ['$scope', '$state', '$stateParams',
function ($scope, $state, $stateParams) {
$log.debug(angular.toJson($stateParams, true));
$http.get(url).success(function(data) {
console.log('data is loaded');
});
}
]);
my routing setup:
var catalogApp = angular.module('catalogApp', ['ui.router', 'ngResource', 'ngSanitize', 'catalogControllers']);
catalogApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '?producttype&colors&models',
controller: 'catalogCtrl',
onEnter: function() {
console.log('route entered');
}
});
$urlRouterProvider.otherwise("/");
}]);
So when i go to url like http://mysite.lc/catalog/#/?producttype=4&colors=5,7,9&models=17 console shows
{} from catalogCtrl, then
data is loaded from catalogCtrl $http.get, and only after that
route entered
If i do log it that way (at router config) controller: function($stateParams) { console.log($stateParams); } it doesn't output anything.
I would suggest resolving the $stateParams on its corresponding state on the .config section. Also,if it is your intention, you need to make your url name part of the url property and not just the stateParameters because ui-router doesn't automatically assign 'home' just because you declared it to be the home state. You can also make the parameters optional by declaring the 'params' property and setting everything to null.
var catalogApp = angular.module('catalogApp', ['ui.router','ngResource', 'ngSanitize', 'catalogControllers']);
catalogApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
params: {
productType: null,
colors: null,
models: null
}
url: '/home?productType&colors&models',
controller: 'catalogCtrl',
resolve: {
parameters: function($stateParams){
return $stateParams;
}
}
});
$urlRouterProvider.otherwise("/");
}]);
and to view it in the console, I would suggest using $log to preserve the sequence of it (I had this problem before using console.log) and angular.toJson to display the data;
var catalogControllers = angular.module('catalogControllers', []);
catalogControllers.controller('catalogCtrl', ['$scope', 'parameters', '$log'
function ($scope, parameters, $log) {
$log.debug(angular.toJson(parameters, true));
//the 'true' arguments makes the json 'prettyfied'
}
]);
let me know how it goes because it's a bit unorthodox for me to create the app which doesn't start from the root page.
I've found what was the problem, and it's small and easy (as usually :) ).
Actually everything worked fine, but i had some unmentioned $http.get calls in code above, and my logging was before any data has been loaded, so firstly i logged my $stateParams and only after that (after data has been loaded) my "home" state has been achieved.
So solution is to put work with $stateParams into $http.get(..).success() function.
So now my controller looks like
var catalogControllers = angular.module('catalogControllers', []);
catalogControllers.controller('catalogCtrl', ['$scope', '$state', '$stateParams',
function ($scope, $state, $stateParams) {
$http.get(url).success(function(data) {
console.log('data is loaded');
$log.debug(angular.toJson($stateParams, true));
});
}
]);
router:
var catalogApp = angular.module('catalogApp', ['ui.router', 'ngResource', 'ngSanitize', 'catalogControllers']);
catalogApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '?producttype&colors&models',
controller: 'catalogCtrl',
onEnter: function() {
console.log('route entered');
}
});
$urlRouterProvider.otherwise("/");
}]);
URL like http://mysite.lc/catalog/#/?producttype=4&colors=5,7,9&models=17 now outputs in console this:
data is loaded from $http.get in controller
route entered from router
{ "producttype": "4", "colors": "5,7,9", "models": "17" } from $stateParams in controller
Please add your comment, because i'm really newbie in angular and will appreciate any opinions on that.
I use routeProvider in Angular JS:
.when('/profile/personal', {
templateUrl: 'personal.html',
controller: 'EditProfileController'
})
How I can pass param to controller EditProfileController and here call Ajax method that returns data. This data must be display in template of route in personal.html.
Example:
.controller('EditProfileController', ['$scope', '$http', function ($scope, $http) {
// If was click on `/profile/personal` from route, must get patam `personal` and call method GetAjax(param);
$scope.GetAjax = function (param){
//here return data put it in template HTML from route
}
}]);
My links are located in page by path:
http://wd.tk/profile
When I click to link route I get URL:
http://wd.tk/profile#/profile/personal/1
Id do:
.controller('EditProfileController', ['$scope', '$http', function ($scope, $http, $routeParams) {
console.log($routeParams);
}]);
I get error:
TypeError: Cannot read property 'idProfile' of undefined
First, in your url configuration, you must put the parameter of url:
when('/profile/personal/:idProfile', {
templateUrl: 'personal.html',
controller: 'EditProfileController'
})
Now, in your EditProfileController, you should get the parameter and call to ajax request:
Inject the $routeParams object and get the parameter with
$routeParams.idProfile:
.controller('EditProfileController',
function ($scope, $http, $routeParams) {
var idProfile = $routeParams.idProfile;
$http.get("service url").then(setData, setError);
function setData(data) {
$scope.data = data;
}
function setError() {
alert("error on get data profile");
}
}]);
In your html, you will show your data profile in the correct format.
But I recommend that all the ajax calls should groups in angular services.
PD:
Check It out angular ui router:
What is the difference between angular-route and angular-ui-router?
hope this helps.
You you need to change your $routeProvider, that should have /profile/:type instead of /profile/personal that means you are going to provide value for :type which can be accessible by injectin $routeParams service inside controller.
.when('/profile/:type', {
templateUrl: 'personal.html',
controller: 'EditProfileController'
})
Controller
.controller('EditProfileController', ['$scope', '$http', '$routeParams', function ($scope, $http, $routeParams) {
$scope.profileType = $routeParams.type;
$scope.GetAjax = function (){
//here you have profile type in $scope.profileType
//you can pass it to ajax by simply accessing scope variable.
$http.get('/getprofiledata?type='+ $scope.profileType)
.success(function(data){
$scope.profileData = data;
})
.error(function(error){
console.log('error occured')
})
}
$scope.GetAjax(); //calling it on controller init
}]);
Update
YOu had missed one of dependency $routeParams in array annotation.
.controller('EditProfileController', ['$scope', '$http', '$routeParams',function ($scope, $http, $routeParams) {
console.log($routeParams);
}]);
I understand how to use Restangular in a controller, however my thoughts are that Restangular is essentially an ORM on steroids.
The ORM shouldn't have any knowledge of the state of the application. That is the job of the controller.
I also want to re-use queries to the ORM, and as such, I believe that Restangular should be used inside a service.
My problem is that I am a js / angularjs and restangular noob, having only about 2-3 months exp with anything front-end.
My Controllers:
app.controller('AdminSupplierIndexController',
['$scope', '$stateParams', '$state', 'Supplier',
function ($scope, $stateParams, $state, Supplier) {
$state.reload();
Supplier.getAll.then(function (suppliers) {
$scope.suppliers = suppliers;
});
}]);
app.controller('AdminSupplierDetailController',
['$scope', '$stateParams', 'Supplier',
function ($scope, $stateParams, Supplier) {
Supplier.getOne({ supplierId : $stateParams.supplierID}).then(function(supplier) {
$scope.supplier = supplier;
});
}]);
My Factory
app.factory('Supplier', ['Restangular', function (Restangular) {
return {
getAll: Restangular.all('supplier').getList(),
getOne: Restangular.one('supplier', supplierId).get()
};
}]);
My Supplier.getAll method works fine - I can list all the suppliers from the Supplier factory.
My problem is with my Supplier.getOne method.
Question 1: How do I inject the supplierId into the factory? I am getting ReferenceError: supplierId is not defined
Question 2: Am I trying to over-engineer things considering that I would have to create individual methods for C-R-U-D for every single factory when these methods are already provided by Restangular?
I know this is old, but an alternate way would just be to wrap it within a function. This way, you can keep any other logic within the service/method.
app.factory('Supplier', ['Restangular', function (Restangular) {
return {
getAll: Restangular.all('supplier').getList(),
getOne: function(supplierId) {
return Restangular.one('supplier', supplierId).get()
}
};
}]);
Found the solution in https://github.com/mgonto/restangular#decoupled-restangular-service
Essentially, the way I have solved this problem is as follows:
app.js
$stateProvider
...
.state('admin.supplier', {
url : "/supplier",
templateUrl : 'templates/admin/supplier/index.html',
controller: "AdminSupplierIndexController",
resolve: {
suppliers: ['Supplier', function(Supplier) {
return Supplier.getList();
}]
}
})
.state('admin.supplier.detail', {
url : "/:supplierId",
templateUrl : "templates/admin/supplier/detail.html",
controller: "AdminSupplierDetailController",
resolve: {
supplier : ['Supplier', '$stateParams', function(Supplier, $stateParams) {
return Supplier.one($stateParams.supplierId).get();
}]
}
})
...
Supplier.js
app.factory('Supplier', ['Restangular', function(Restangular) {
return Restangular.service('supplier');
}]);
SupplierControllers.js
app.controller('AdminSupplierIndexController', ['$scope', '$stateParams', '$state', 'suppliers',
function ($scope, $stateParams, $state, suppliers) {
$state.reload();
$scope.suppliers = suppliers;
}]);
app.controller('AdminSupplierDetailController', ['$scope', 'supplier',
function ($scope, supplier) {
$scope.supplier = supplier;
}]);
My style of writing angular controllers is like this (using controller name instead of function)
angular.module('mymodule', [
])
.controller('myController', [
'$scope',
function($scope) {
// Some code here
}
]);
What I need now is when providing i routes I want to define resolve part:
$routeProvider.when('/someroute', {
templateUrl: 'partials/someroute.html',
resolve: myController.resolve}) // THIS IS THE CRITICAL LINE
Since controller is defined as a name how to accomplish resolve part bellow?
To clarify more in details I want to load some data from server before route is resolved and then use these data in controller.
UPDATE: To be more precise I want each module has its "resolve" function that will be called before root with that controller is executed. Solution in this post (answered by Misko Hevery) does exactly what I want but I don't have controllers as functions but as a names.
The controller definition and resolve parts are to be specified separately on the route definition.
If you define controllers on a module level you need to reference them as string, so:
$routeProvider.when('/someroute', {
templateUrl: 'partials/someroute.html',
controller: 'myController',
resolve: {
myVar: function(){
//code to be executed before route change goes here
};
});
The above code also shows how to define a set of variables that will be resolved before route changes. When resolved those variables can be injected to a controller so taking the example from the snippet above you would write your controller like so:
.controller('myController', ['$scope', 'myVar', function($scope, myVar) {
// myVar is already resolved and injected here
}
]);
This video might help as well: http://www.youtube.com/watch?v=P6KITGRQujQ
#pkozlowski.opensource 's answer works, but I don't really want to mess up my routing and and controllers, because I always keep it separated (from Yo Generator). Actually, we can also have controller and resolve(r) all as string/name (NOT function).
angular.module('mymodule', [
])
.controller('myController', [
'$scope', 'myModelCombination'
function($scope, myModelCombination) {
// myModelCombination[0] === (resolved) myModel
// myModelCombination[1] === (resolved) myModel2
}
])
.controller('myController2', [
'$scope', 'myModel'
function($scope, myModel) {
// Some code here
}
])
.factory('myModel', [
'$scope',
function($scope) {
// return a promise
}
])
.factory('myModel2', [
'$scope',
function($scope) {
// return a promise
}
])
.factory('myModelCombination', [
'$scope', 'myModel', 'myModel2'
function($scope) {
return $q.all(['myModel', 'myModel2']);
}
]);
Then in your routing file this should be added
$routeProvider.when('/someroute', {
templateUrl: 'partials/someroute.html',
resolve: ['myModel'] //ALWAYS IN ARRAY)
});
$routeProvider.when('/myModelCombination', {
templateUrl: 'partials/someroute2.html',
resolve: ['myModel'] //ALWAYS IN ARRAY)
});
http://docs.angularjs.org/api/ng.$routeProvider
This would work too
var MyController = myApp.controller('MyController', ['$scope', 'myData', function($scope, myData) {
// Some code here
}]);
MyController.resolve = {
myData: ['$http', '$q', function($http, $q) {
var defer = $q.defer();
$http.get('/foo/bar')
.success(function(data) {
defer.resolve(data);
})
.error(function(error, status) {
defer.reject(error);
});
return defer.promise;
}]
};
#TruongSinh answer worked for me and is way nicer than having additional functions in the router. I tweaked it a little as it was returning the deferred object instead of the actual resolved data.
$routeProvider.when('/someroute', {
templateUrl: 'partials/someroute.html',
controller: 'SomeController',
resolve: {
myModel: 'myModel'
}
});