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
Related
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
});
I have a small problem, I am using a route like
.when('/beta/profiles/new', {
controller: 'ProfilesController',
controllerAs: 'vm',
templateUrl: 'profiles/new.html',
resolve: {
action: function() { return "new"; }
}
})
.when('/beta/profiles/index', {
controller: 'ProfilesController',
controllerAs: 'vm',
templateUrl: 'profiles/index.html',
resolve: {
action: function() { return "index"; }
}
})
now in my controller I have something like this:
function ProfilesController(profileService, action) {
var vm = this,
permittedActions = ["index", "new"];
var actions = {
index: function() {
vm.hideProfile = {
currentProfile: null,
showModal: false,
setCurrentProfile: function(profile_id) { this.currentProfile = profile_id },
toggleHide: function() {/*...*/}
}
profileService.all().then(function(data) {
vm.profiles = data.result;
})
},
new: {/*.. */}
}
if(permittedActions[action] > -1) actions[action]();
}
now my question is if I hit a link (in the header,lets say) multiple times in succession, each time I hit profiles/index.html should it re-initialize everything ?
if I want to takethe advantage of angular's dirty checking thing should I put the vm.hideProfile out of the index function ? and If I do that should I also not do the same with vm.profiles = [];?
how/what can I do to check if variables are getting re-initialized and angular's dirty checking is not in play, or is it just common sense!! or should I just have a separate controller for each action?
and in case of re-initialization, is there any better way so that I can keep the hideProiles inside the index because I really don't need my new and show actions to know about hideProfile, since its unnecessary?
A few things that hopefully can help:
Dirty checking is for objects bound to an active $scope (and $rootScope). https://docs.angularjs.org/guide/scope
To use $scope in your controller, (a) inject it. e.g. ProfilesController($scope, profileService, action), (b) put things on it, e.g. $scope.vm = vm;
looking at your code, the raw initialization should be super fast.
Don't think javascript is secure. If security is a real concern for your app, protect at the server.
Lastly on app arch, since angular is an MVC framework: define functions in your controller, and connect them to actions in the view via $scope. eg
$scope.index = function() { // do stuff
if (action !== "index") {return}; // disallowed by route
//continue
}
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 :)