In a Angular App i am using ui-router to handle navigation etc.
In a separate script file, i have a function like so;
$(function () {
function doSomething(){
if ($('.thisclass').length) {
$('.thisclass').css({ 'height': someHeight });
}
}
});
My problem is, that whenever the state changes, i want to run the above function. But as it is not part af any Angular function, i get an error when i reference it, as i cannot find it.
What should i be doing, instead of the above?
Hello you can also add your jquery code into the onEnter:function() of your state , as onEnter is executed each time you change the state and a controller is loaded.
example(a login state):
.state('login', {
url: '/login',
controller: 'LoginCtrl',
templateUrl: '/assets/modules/login/login.html',
resolve: {
user: ['authService', '$q', function (authService, $q) {
if (authService.user) {
return $q.reject({authorized: true});
}
}]
},
onEnter: function () {
//i hide header tabs, you can add your code here
$('.container-fluid').css('display', 'none');
},
onExit: function () {
//onExit is executed when we leave that state and go to another
}
});
Hope helps, good luck.
Here is how I would do.
app.js
(function(){
angular.module('app',[]);
/* other code like configuration etc */
})();
SomeService.js
(function(){
angular.module('app');
.factory('someService',function(){
return {
doSomething: function(){
$('.container-fluid').css('display', 'none');
}
};
});
})();
app.run.js
(function(){
angular.module('app')
//Inject your service here
.run(function($rootScope,someService){
//Look for successful state change.
//For your ref. on other events.
//https://github.com/angular-ui/ui-router/wiki#state-change-events
$rootScope.$on('$stateChangeSuccess', function() {
//If you don't wanna create the service, you can directly write
// your function here.
someService.doSomething();
});
})
})();
Always wrap your angular code within IIFE it wraps everything in closure and prevents leaks as well as provides a layer of security.
Hope this helps!
If you are the one controlling the state changes via $state.go() for example, you can amend it:
$state.go('somewhere', { 'place': 'somewhere' }).then(() => {
// write your function here
});
Related
I'm want to launch a code if any modal opens. Commonly I'm want something like:
$scope.$watch(function () {
return $modal.isOpenState;
}, function (val) {
//my code here
}, true
);
But I'm didn't know what to watch. Yes, I can detect open event for each instance, like:
modalInstance.opened.then(function() {
//my code here
});
But this isn't DRY.
P.S. Also I'm can make something like $('.modal').hasClass('in') in $watch function, but this is little bit ugly
P.P.S And btw I'm using ui-router to open modals (see faq here)
$modal.open({
templateUrl: "...",
resolve: {... },
controller: function($scope) { ... }
}).result.finally(function() {
//can put code here, but same issue
});
There is an internal service UI modal uses called $modalStack. This service is used by $modal and has method called getTop to retrieve currently opened modal instance. So you can inject this service and simply watch getTop result on $rootScope. For example:
app.run(function($rootScope, $modalStack) {
$rootScope.$watch($modalStack.getTop, function(newVal, oldVal) {
if (newVal) {
console.log('opened', newVal, oldVal);
}
});
});
Demo: http://plnkr.co/edit/ZVD0cryL0qXNGl9UPMxB?p=preview
I'm facing the following situation in my Angular application and I would like to have some advices here.
I have a page where I show some products, this page is managed by a controller called 'ProductsController'. This controller has a method called 'showProductDetails' which is called once the user clicks on a specific product, and the goal of this method is just to retrieve the details of the product and to display these details in a modal panel.
Nothing really special until here. The problem is that because of modularity I would like to attach a different controller to the modal panel, and to manage all the logic of this modal panel in the new controller, in this case 'ProductDetailController'. The problem is that I retrieve the data of the product before opening the modal panel, but as I retrieve this data in the scope of the first controller, from the second controller I cannot access to the product that I have previously retrieved. I've been told that to share data between controllers in angularJs is done through services, but I don't see how a stateless service can help me here.
Here is my code to understand better the situation:
The first controller:
app.controller('ProductsController', ['$scope','productsFactory','commonFactory','productsFactoryHelper','$filter','$modal',function ($scope,productsFactory,commonFactory,productsFactoryHelper,$filter,$modal)
{
$scope.showProductDetails = function (size,product) {
$scope.showLoader('Loading the details of the product. Please wait...');
productsFactoryHelper.Product.query({id:product.id},function(response)
{
$scope.selectedProduct=response;
$scope.hideLoader();
var modalInstance = $modal.open({
templateUrl: 'productDetail.html',
controller: 'ProductDetailController',
size: size
});
},function(error)
{
commonFactory.Pop('error','This product is not available at this moment. Please try again later. If the problem persists contact a system administrator');
$scope.hideLoader();
});
};
_init();
}]);
And the second controller:
app.controller('ProductDetailController',['$scope','$modalInstance', function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]);
So basically the question is how can access from the 'ProductDetailController' to the object 'selectedProduct' which is in the scope of the 'ProductsController'.
Thank you for your help.
Use resolve of the $modal to send your data to the new controller like below.
app.controller('ProductsController', ['$scope','productsFactory','commonFactory','productsFactoryHelper','$filter','$modal',function ($scope,productsFactory,commonFactory,productsFactoryHelper,$filter,$modal)
{
$scope.showProductDetails = function (size,product) {
$scope.showLoader('Loading the details of the product. Please wait...');
productsFactoryHelper.Product.query({id:product.id},function(response)
{
$scope.selectedProduct=response;
$scope.hideLoader();
var modalInstance = $modal.open({
templateUrl: 'productDetail.html',
controller: 'ProductDetailController',
size: size,
resolve:{
"selectedProduct":response
}
});
},function(error)
{
commonFactory.Pop('error','This product is not available at this moment. Please try again later. If the problem persists contact a system administrator');
$scope.hideLoader();
});
};
_init();
}]);
I dont know about the producfactory helper product query has a promise if it has a promise you can use like this..
$scope.showProductDetails = function (size,product) {
$scope.showLoader('Loading the details of the product. Please wait...');
var modalInstance = $modal.open({
templateUrl: 'productDetail.html',
controller: 'ProductDetailController',
size: size,
resolve:{
"selectedProduct":productsFactoryHelper.Product.query({id:product.id})
}
});
};
And in the ProductDetailController you can inject this selectedProduct like below
app.controller('ProductDetailController',['$scope','$modalInstance','selectedProduct ' function ($scope, $modalInstance,selectedProduct ) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]);
This can indeed be done through services, since they are stateless and keep their data once instantiated.
function productService($http) {
this.products = [];
this.loadProducts() {
$http.get('/url/to/your/product/api').then(function(err, data) {
this.products = data.products;
});
};
this.getProducts = function() {
return this.products;
}
}
angular
.module('yourModule')
.service('productService', productService);
You can then just inject productService in both controllers, load the products using productService.loadProducts(), and get them using productService.getProducts().
This is just an example. Services can be used to share any kind of data.
Services are indeed the answer for you, or you can use pure eventing if you do not need to access the data more then once.
Pure Eventing
app.controller('parentCtrl', function($scope) {
// Do something
// Action completed
#scope.$emit('someactionComplete', data);
});
app.controller('childCtrl', function($scope) {
$scope.$on('someactionComplete', function(data) {
// Process data
});
});
Using a service. The advantage of using a service is that the data is persisted.
app.controller('parentCtrl', function($scope, MyService) {
// Do something
// Action completed
MyService.setData(data);
#scope.$emit('someactionComplete');
});
app.controller('childCtrl', function($scope) {
$scope.$on('someactionComplete', function() {
MyService.getData(data);
});
});
You could further enhance this were the service loaded the data and returns a promise in the getter.
Update: this should be possible in angular-ui-router as of 1.0.0alpha0. See the release notes https://github.com/angular-ui/ui-router/releases/tag/1.0.0alpha0 and the issue https://github.com/angular-ui/ui-router/issues/1018 I created.
I would like to access the state's name and other attributes the app is navigating to using angular ui-router when working on the resolve.
The reason: I want load some user data (including their access rights) asynchronously before allowing the app the enter that page.
Currently this is not possible because injecting $state into the resolve points to the state you're navigating away form, not to the one you're navigating to.
I know I can:
get the toState somewhere else with $rootScope('$stateChangeStart') and save it in my settings service for instance. But I think it's a little messy.
hard code the state into the resolve, but I don't want to reuse my resolve for all pages
I also created an issue on the ui-router github (Please + 1 if you are interested!):
https://github.com/angular-ui/ui-router/issues/1018
Here's my code so far. Any help appreciated!
.config(function($stateProvider) {
$stateProvider.state('somePage', {
// ..
resolve: {
userData: function($stateParams, $state, Settings) {
return Settings.getUserData() // load user data asynchronously
.then(function (userData) {
console.log($stateParams);
console.log($state);
// Problem: $state still points to the state you're navigating away from
});
}
}
});
});
Update for Ui-Router 1.x
$provide.decorator('$state', ($delegate, $transitions) => {
$transitions.onStart({}, (trans) => {
$delegate.toParams = trans.params()
$delegate.next = trans.to().name
})
return $delegate
})
Ui-Router 0.x
You can always decorate $state with next and toParams properties:
angular.config(function($provide) {
$provide.decorator('$state', function($delegate, $rootScope) {
$rootScope.$on('$stateChangeStart', function(event, state, params) {
$delegate.next = state;
$delegate.toParams = params;
});
return $delegate;
});
});
And use as such:
.state('myState', {
url: '/something/{id}',
resolve: {
oneThing: function($state) {
console.log($state.toParams, $state.next);
}
}
});
So I discovered the answer to this myself. If you're code is behaving like mine, the $stateParams object is properly injected, but $state is an empty (or old) state object.
What worked for me was referencing this in the resolve function:
.state('myState', {
url: '/something/{id}',
templateUrl: '/myTemplate.html',
controller: function() {},
resolve: {
oneThing: function($stateParams) {
console.log($stateParams); // comes through fine
var state = this;
console.log(state); // will give you a "raw" state object
}
}
})
The first log will return what you'd expect. The second log will return a "raw" (for lack of a better term) state object. So, for instance, to get the state's name, you can access, this.self.name.
I realize this isn't preferred...it would be a lot nicer if $state (or another standardized object) could provide this information for us at the resolve, but this is the best I could find.
Hope that helps...
this.toString() will give you the state name
This has been asked here.
It looks like they built into 1.0.0-rc.2 $state$ which you can inject into the resolve function and get this information.
resolve: {
oneThing: function($state$) {
console.log($state$);
}
}
I'm trying calling the /auth/logout url to get redirected after session is deleted:
app.config(['$routeProvider',function($routeProvider) {
$routeProvider
.when('/auth/logout',{
controller:'AuthLogout'
//templateUrl: not needed
})
})
.controller('AuthLogout', ['$window','$location', function ($window,$location) {
$window.localStorage.removeItem('user_username');
$window.localStorage.removeItem('user_id');
$window.localStorage.removeItem('user_session_token');
$location.path('/');
}]);
I actually don't need a view for AuthLogout controller but if I do not specify the templateUrl in routeProvider I can't get this to work, while if I specify a templateUrl it works.
How can I call the url/controller without to having to load a view??
You could do :
.when('/auth/logout', {
controller: function(){
//do staff
}
})
btw may be there is something wrong in your code
because template works and you could exploit it in
the same way
http://docs.angularjs.org/api/ngRoute/provider/$routeProvider
You can use a resolve handler according to the post https://github.com/angular/angular.js/issues/1838
Checkout this quick example and notice the alert statement in resolve.
http://jsfiddle.net/Wk7WD/34/
.when('/detail/:id/', {
resolve: {
load: function ($route, dataService) {
alert("hello");
//Your statements instead of all this which I found in an example
return dataService.load($route.current.params.id);
}
}
})
Instead of the alert you can have your own statements
use redirectTo
app.config(['$routeProvider',function($routeProvider) {
$routeProvider
.when('/auth/logout',{
redirectTo:'/'
})
});
Hope this will work for you :)
A bit of info. I'm working on a single page app, but am attempting to make it just an HTML file, rather than an actual dynamic page that contains all the bootstrap information in it. I'm also hoping to, when the app boots (or perhaps prior to), check to see if the current session is 'logged in', and if not then direct the hash to the 'login'.
I'm new to Angular, and am having a difficult time figuring out how to program out this flow. So, in essence..
HTML page loaded with 'deferred' bootstrap
Hit URL to get login status
If status is 'not logged in', direct to #/login
Start app
Any pointers on where #2 and #3 would live? In my 'easy world' I'd just use jquery to grab that data, and then call the angular.resumeBootstrap([appname]). But, as I'm trying to actually learn Angular rather than just hack around the parts I don't understand, I'd like to know what would be used in this place. I was looking at providers, but I'm not sure that's what I need.
Thanks!
EDIT
Based on #Mik378's answer, I've updated my code to the following as a test. It works to a point, but as the 'get' is async, it allows the application to continue loading whatever it was before then shooting off the status results..
var app = angular.module('ping', [
'ngRoute',
'ping.controllers'
]).provider('security', function() {
this.$get = ['$http', function($http) {
var service = {
getLoginStatus: function () {
if (service.isAuthenticated())
return $q.when(service.currentUser);
else
return $http.get('/login/status').then(function (response) {
console.log(response);
service.loggedIn = response.data.loggedIn;
console.log(service);
return service.currentUser;
});
},
isAuthenticated: function () {
return !!service.loggedIn;
}
};
return service;
}];
}).run(['security', function(security) {
return security.getLoginStatus().then(function () {
if(!security.isAuthenticated()) {
console.log("BADNESS");
} else {
console.log("GOODNESS");
}
});
}]);
My hope was that this could somehow be completed prior to the first controller booting up so that it wasn't loading (or attempting to load) things that weren't even cleared for access yet.
EDIT #2
I started looking into the 'resolve' property in the router, and #Mik378 verified what I was looking at. My final code that is (currently) working how I want it is as follows (appologies about the super long code block)
angular.module('ping.controllers', [])
.controller('Dashboard', ['$scope', function($scope) {
console.log('dashboard')
}])
.controller('Login', ['$scope', function($scope) {
console.log('login')
}]);
var app = angular.module('ping', [
'ngRoute',
'ping.controllers'
]).run(['$rootScope', '$location', function($root, $location) {
$root.$on("$routeChangeError", function (event, current, previous, rejection) {
switch(rejection) {
case "not logged in":
$location.path("/login"); //<-- NOTE #1
break;
}
});
}]);
app.provider('loginSecurity', function() {
this.$get = ['$http', '$q', function($http, $q) {
var service = {
defer: $q.defer, //<-- NOTE #2
requireAuth: function() { //<-- NOTE #3
var deferred = service.defer();
service.getLoginStatus().then(function() {
if (!service.isAuthenticated()) {
deferred.reject("not logged in")
} else {
deferred.resolve("Auth OK")
}
});
return deferred.promise;
},
getLoginStatus: function() {
if (service.isAuthenticated()) {
return $q.when(service.currentUser);
} else {
return $http.get('/login/status').then(function(response) {
console.log(response);
service.loggedIn = response.data.loggedIn;
console.log(service);
return service.currentUser;
});
}
},
isAuthenticated: function() {
return !!service.loggedIn;
}
};
return service;
}
];
});
app.config(['$routeProvider', function($routeProvider) {
console.log('Routing loading');
$routeProvider.when('/', {
templateUrl: 'static/scripts/dashboard/template.html',
controller: 'Dashboard',
resolve: {'loginSecurity': function (loginSecurity) {
return loginSecurity.requireAuth(); //<- NOTE #4
}}
});
$routeProvider.when('/login', {
templateUrl: 'static/scripts/login/template.html',
controller: 'Login'
});
$routeProvider.otherwise({redirectTo: '/404'});
}]);
Notes:
This section hooks into routing failures. In the case of a "no login", I wanted to catch the failure and pop the person over to the login page.
I can't get access to the $q inside of the requireAuth function, so I grabbed a reference to it. Perhaps a better way of doing this exists?
This function wraps up the other two - it uses the promise returned from getLoginStatus, but returns its own promise that will be rejected if the end result from the getLoginStatus winds up with the user not being logged in. Sort of a round-about way of doing it.
This returns #3's promise, which is used by the $routeProvider.. so if it fails, the routing fails and you end up catching it at #1.
Whew. I think that's enough for a day. Time for a beer.
No need to use a deferred bootstrap for your case:
angular.module('app').run(['security', '$location', function(security) {
// Get the current user when the application starts
// (in case they are still logged in from a previous session)
security.requestCurrentUser().then(function(){
if(!security.isAuthenticated())
$location.path('yourPathToLoginPage')
}; //service returning the current user, if already logged in
}]);
this method requestCurrentUser would be the following:
requestCurrentUser: function () {
if (service.isAuthenticated())
return $q.when(service.currentUser);
else
return $http.get('/api/current-user').then(function (response) {
service.currentUser = response.data.user;
return service.currentUser;
});
}
and inside security service again:
isAuthenticated: function () {
return !!service.currentUser;
}
Note the run method of the module => As soon as the application runs, this service is called.
-- UPDATE --
To prevent any controller to be initialized before the promise provided by requestCurrentUser is resolved, a better solution, as evoked in the comments below, is to use the resolve route property .