I have the following controller code:
applicationControllers.controller('PostsController', ['$scope', '$http', function
($scope, $http) {
var page = 1;
$scope.init = function() {
this.loadPage(page);
}
$scope.nextPage = function() {
page++;
this.loadPage(page);
}
$scope.previousPage = function() {
page--;
if (page <= 0) { page = 1 }
this.loadPage(page);
}
$scope.filterByProvince = function(provinceName) {
console.log(provinceName);
}
$scope.loadPage = function(page) {
$http.get('/posts.json?page=' + page).success(function(data) {
$scope.posts = data;
});
}
$scope.init();
}]);
The problem is when using a ng-click directive to filterByProvince('test'), it seems the init function are also called. I want to avoid this behaviour.
Any help?
In Angular, when you want to have an <a> element display like an <a> element, but used differently (e.g. as a button (by attaching some ngClick directive)), you should use an empty href:
Do something
The problem with using href="#" is that it is eihter:
Recognized by ngRoute (if it is used) as a "link" to the home route ('/'), so even if you are in that route it gets reloaded causing the effects of the ngClick callback to be voided or
Recognized by the browser as link with an anchor tag to the same page, so clicking it causes the page to reload (again voiding any effect of the ngClick callback).
I am not sure how ui-router handles this, but I am not a big fan of ui-router - maybe someone with more experience can give some feedback.
Related
I have small problem to solve.
I have modal controller rejectIssueModalCtrl.js
(function () {
'use strict';
function rejectIssueModalCtrl($modalInstance, issue, $rootScope) {
var self = this;
self.cancel = function () {
$modalInstance.dismiss('cancel');
};
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
}
rejectIssueModalCtrl.$inject = ['$modalInstance', 'issue', '$rootScope'];
angular
.module('app')
.controller('rejectIssueModalCtrl', rejectIssueModalCtrl);
})();
When I click the button I can open this modal and write a reason. I want to show this reject reason in table in other controller.
Here's my code from other controller issueDetailsCtrl.js
$scope.reasonoo = $rootScope.reasono;
function rejectIssue() {
var rejectModal = $modal.open({
templateUrl: '/App/Issue/rejectIssueModal',
controller: 'rejectIssueModalCtrl',
controllerAs: 'rejectModal',
size: 'lg',
resolve: {
issue: self.issueData
}
});
rejectModal.result.then(function (reason) {
issueSvc
.rejectIssue(self.issueData.id, reason)
.then(function (issueStatus) {
changeIssueStatus(issueStatus.code);
getIssue();
}, requestFailed);
});
};
and html code
<div>
<span class="right" ng-bind="$root.reasono"></span>
</div>
As you can see I tried to use $rootScope. I can console.log the reason but I cant make it to show in this html. Any help?
We're missing some context, but I believe this is your problem:
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
Assuming self.reason is bound to the input in your modal, it won't be defined outside of the reject callback - that's the nature of async. You're able to log to console because you're doing so within the callback.
Define $rootScope.reasono inside of the callback like so:
self.reject = function () {
$modalInstance.close(self.reason);
console.log(self.reason);
$rootScope.reasono = self.reason;
};
Edited to show that $rootScope should be removed as a named parameter in the reject function definition.
Using root scope is not recommended. For this reason it is recommended to create a service for intercommuncation with variable to store reject reason, then inject this service for each controller - that way you will be able to read/write reason from different controllers.
I'm using a module from the UI Boostrap extensions (http://angular-ui.github.io/bootstrap). The module actually serves as a loading dialog and is automatically closed when a set of web service data is returned to my Angular code. As the data on this page is loaded automatically the dialog comes up immediately.
All this works great when I hit the page in question for the the first time or simply refresh it. The problem occurs when I go to a deeper page and then try and navigate back to the original page (with the dialog) via the browser's back button. The dialog never goes away despite all the fact that all the data is returned and the module's dismiss() call has been made.
I've traced this down to the promise to open the dialog appears to be happening after the dismiss call but, again, only when the page is loaded via the back button. The dismiss call never closes anything because it hasn't been added yet (I've confirmed this in the debugger).
The question I have is how could I handle this? Is there a solid way to catch the completion of the page loading via Angular and double check that the dialog closed? Is there a better way via UI Bootstrap's api?
I know this is rather unusual case but any thoughts on it would be great.
Thanks!
#HankScorpio's solution is good, but I think there may be a simplified option now.
There is no need to store the current modal anymore, if you register either a $locationChangeStart or $routeChangeStart listener with $uibModalStack injected and call $uibModalStack.dismissAll(). $locationChangeStart has the benefit of working for both ngRoute and uiRoute.
i.e. If only for the one page, then in your controller you'd have:
angular.module('app')
.controller('ctrl', ['$scope', '$uibModalStack', ctrl]);
function ctrl($scope, $uibModalStack) {
$scope.$on('$locationChangeStart', handleLocationChange);
function handleLocationChange() {
$uibModalStack.dismissAll();
}
}
If you want to do this for all pages then define this in a factory that is always loaded or just an app.run code segment:
angular.module('app')
.run(['$rootScope', '$uibModalStack', setupUibModal]);
setupUibModal($rootScope, $uibModalStack) {
$rootScope.$on('$locationChangeStart', handleLocationChange);
function handleLocationChange() {
$uibModalStack.dismissAll();
}
}
Here is the simple solution when using ui-router for state change
Closing modal popup on the back button click in angularjs
App.run(['$rootScope', '$modalStack', function ($rootScope, $modalStack) {
$rootScope.$on('$stateChangeStart', function (event) {
var top = $modalStack.getTop();
if (top) {
$modalStack.dismiss(top.key);
}
})
}]);
hope this will save lot of time for people who are breaking heads
I've run into this same problem. Here's how I fixed it.
1) Create a service to abstract the opening and closing of a modal and track which one is open (necessary for step 2). Instead of calling $modal.open() directly, call ModalService.open().
Here you go, you can have the one I wrote:
(function () {
'use strict';
var theModule = angular.module('services.modalService', ['ui.bootstrap']);
theModule.factory('ModalService', function ($modal) {
var service = {};
var currentModal;
var clearModal = function () {
currentModal = undefined;
};
service.getCurrentModal = function () {
return currentModal;
};
service.open = function (options) {
currentModal = $modal.open(options);
currentModal.result['finally'](clearModal);
return currentModal;
};
return service;
});
}());
2) In a controller, add an event listener to $routeChangeStart, this event will fire whenever someone hits the back button.
$scope.$on('$routeChangeStart', function(){
var currentModal = ModalService.getCurrentModal();
if(angular.isDefined(currentModal)){
currentModal.dismiss('cancel');
}
});
3) Your modals should now close when a user hits back.
4) Enjoy.
IMPROVEMENT:
I found the answer from HankScorpio to be the best out there. I wanted to include this snippet for those using ui-router and their recommendation for stateful modals.
1) I wanted the result.finally(...) to jump to a parent state;
2) I wanted to control the closing of the modal from $stateProvider config, NOT through rigging a controller and adding a listener to $routeChangeStart
Here is an example of a state that opens (and closes) it's modal:
.state('product.detail', {
url: '/detail/{productId}',
onEnter: /*open-modal logic*/,
onExit: ['ModalService', function (ModalService) { ModalService.close()} ]
})
I made ModalService aware of $state so that the result of closing a modal could jump to a parent view:
a. Add an isStateful flag to modalService.open(...):
service.open = function (options, isStateful) {
currentModal = $uibModal.open(options);
currentModal.result.finally(function () {
clearModal(isStateful);
});
return currentModal;
};
so that clearModal will return to previous state:
var clearModal = function (isStateful) {
currentModal = undefined;
if (isStateful)
$state.go('^');
};
Finally, add the closeModal() function called above (not a "stateful" close, simply a dismissal):
service.close = function() {
if (currentModal) {
currentModal.dismiss().then(function () {
clearModal();
})
}
}
The benefits of this are that back button functionality is controlled at state config level, not through a listener.
I believe I am experiencing the same issue mentioned here: $anchorScroll and $location only work after second try
I reviewed the plunker that works and I have routing in place, but it is still taking two clicks. I am using ng-route and not ui-router. How can I prevent it taking two clicks to get anchorScroll to work? As the first wants to cause a route to be established versus scrolling to the appropriate anchor.
Here is the click:
<a data-ng-click="gotoRequests()">Requests</a>
Here is the destination:
<div id="pendingrequests"></div>
Here is the function in my controller:
$scope.gotoRequests = function() {
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('pendingrequests');
// call $anchorScroll()
$anchorScroll();
};
I was able to solve it using one of the answers here: How to handle anchor hash linking in AngularJS
by creating the following function:
$scope.scrollTo = function(id) {
var old = $location.hash();
$location.hash(id);
$anchorScroll();
//reset to old to keep any additional routing logic from kicking in
$location.hash(old);
};
I would call this as follows:
Yipee
<div id="pendingrequests"></div>
Latest Update
From AngularJS 1.4.0 $anchorScroll allows you to directly pass the id as a parameter without the need to update the URL with the hash.
During click
<div data-ng-click="gotoRequests(pendingrequests)"> </div>
In Controller
$scope.gotoRequests = function(divId) { $anchorScroll(divId); }
I also had the same issue with angular 1 and I solved it using $timeout. Here is an example of how I did it
angular.module('app').controller('MyTestController', ['$scope', '$location', '$anchorScroll', '$timeout', function($scope, $location, $anchorScroll, $timeout) {
function scrollToElement (element, offset){
$timeout(function() {
$anchorScroll.yOffset = offset; // add extra pixels to scroll initially
var old = $location.hash();
$location.hash(element);
$anchorScroll();
$location.hash(old);
});
}
scrollToElement('element ID', 100);
}]);
You need to add $timer for 300 like:
this.gotoBottom = function(scrollId) {
$timeout(function() {
$location.hash(scrollId); $anchorScroll(scrollId);
}, 300);
}
I have a single page app that opens a gallery. I want to bind document level keyup event (for keyboard gallery controlls) only when the gallery is open, ie. when route matches
.when('/reference/:galleryId/:imageId/', { templateUrl: '/partials/gallery.htm', controller: 'galleryController', controllerAs: 'reference' })
and I want to unbind it when I leave this route.
One thing that might be a problem is, I block reloading the view between images within the same gallery with this:
.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}])
Demo (Click on "Fotografie" to open the gallery)
http://tr.tomasreichmann.cz/
Angular wiz to the rescue?
Thank you for your time and effort
You could bind a keyup event to $document in your controller's constructor and then unbind the event when the controller's $scope is destroyed. For example:
.controller('galleryController', function ($scope, $document) {
var galleryCtrl = this;
function keyupHandler(keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
$scope.$apply(); // remove this line if not need
}
$document.on('keyup', keyupHandler);
$scope.$on('$destroy', function () {
$document.off('keyup', keyupHandler);
});
...
});
There will be nothing left behind when the view become inactive.
If you feel it isn't right to do this in the controller, you could move this into a custom directive and place it in a template of the view.
Finally I stuck with
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:og="http://ogp.me/ns#"
xmlns:fb="http://www.facebook.com/2008/fbml"
lang="cz"
ng-app="profileApp"
ng-keyup="$broadcast('my:keyup', $event)" >
Not sure if this is good practice, but it registers within my controller
$scope.$on('my:keyup', function(event, keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
});
And doesn't do anything when the current route is not active
I found this answer here: https://groups.google.com/forum/#!searchin/angular/document$20level$20events/angular/vXqVOKcwA7M/RK29o3oT9GAJ
There is another way to bind it globally which wasn't my goal, the original code in question did what I needed.
I have a function which I want to call after page content is loaded. I read about $viewContentLoaded and it doesn't work for me. I am looking for something like
document.addEventListener('DOMContentLoaded', function () {
//Content goes here
}, false);
Above call doesn't work for me in AngularJs controller.
According to documentation of $viewContentLoaded, it supposed to work
Emitted every time the ngView content is reloaded.
$viewContentLoaded event is emitted that means to receive this event you need a parent controller like
<div ng-controller="MainCtrl">
<div ng-view></div>
</div>
From MainCtrl you can listen the event
$scope.$on('$viewContentLoaded', function(){
//Here your view content is fully loaded !!
});
Check the Demo
Angular < 1.6.X
angular.element(document).ready(function () {
console.log('page loading completed');
});
Angular >= 1.6.X
angular.element(function () {
console.log('page loading completed');
});
fixed - 2015.06.09
Use a directive and the angular element ready method like so:
js
.directive( 'elemReady', function( $parse ) {
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
})
}
}
})
html
<div elem-ready="someMethod()"></div>
or for those using controller-as syntax...
<div elem-ready="vm.someMethod()"></div>
The benefit of this is that you can be as broad or granular w/ your UI as you like and you are removing DOM logic from your controllers. I would argue this is the recommended Angular way.
You may need to prioritize this directive in case you have other directives operating on the same node.
You can directly call it by adding {{YourFunction()}} after HTML element.
Here is a Plunker Link.
I had to implement this logic while handling with google charts. what i did was that at the end of my html inside controller definition i added.
<body>
-- some html here --
--and at the end or where ever you want --
<div ng-init="FunCall()"></div>
</body>
and in that function simply call your logic.
$scope.FunCall = function () {
alert("Called");
}
var myM = angular.module('data-module');
myM.directive('myDirect',['$document', function( $document ){
function link( scope , element , attrs ){
element.ready( function(){
} );
scope.$on( '$viewContentLoaded' , function(){
console.log(" ===> Called on View Load ") ;
} );
}
return {
link: link
};
}] );
Above method worked for me
you can call javascript version of onload event in angular js. this ng-load event can be applied to any dom element like div, span, body, iframe, img etc. following is the link to add ng-load in your existing project.
download ng-load for angular js
Following is example for iframe, once it is loaded testCallbackFunction will be called in controller
EXAMPLE
JS
// include the `ngLoad` module
var app = angular.module('myApp', ['ngLoad']);
app.controller('myCtrl', function($scope) {
$scope.testCallbackFunction = function() {
//TODO : Things to do once Element is loaded
};
});
HTML
<div ng-app='myApp' ng-controller='myCtrl'>
<iframe src="test.html" ng-load callback="testCallbackFunction()">
</div>
If you're getting a $digest already in progress error, this might help:
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
if(!$scope.$$phase) {
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
}
else {
var func = $parse(attrs.elemReady);
func($scope);
}
})
}
}
I was using {{myFunction()}} in the template but then found another way here using $timeout inside the controller. Thought I'd share it, works great for me.
angular.module('myApp').controller('myCtrl', ['$timeout',
function($timeout) {
var self = this;
self.controllerFunction = function () { alert('controller function');}
$timeout(function () {
var vanillaFunction = function () { alert('vanilla function'); }();
self.controllerFunction();
});
}]);
Running after the page load should partially be satisfied by setting an event listener to the window load event
window.addEventListener("load",function()...)
Inside the module.run(function()...) of angular you will have all access to the module structure and dependencies.
You can broadcast and emit events for communications bridges.
For example:
module set onload event and build logic
module broadcast event to controllers when logic required it
controllers will listen and execute their own logic based on module onload processes.
If you want certain element to completely loaded, Use ng-init on that element .
e.g. <div class="modal fade" id="modalFacultyInfo" role="dialog" ng-init="initModalFacultyInfo()"> ..</div>
the initModalFacultyInfo() function should exist in the controller.
I found that if you have nested views - $viewContentLoaded gets triggered for every of the nested views. I've created this workaround to find the final $viewContentLoaded. Seems to work alright for setting $window.prerenderReady as required by Prerender (goes into .run() in the main app.js):
// Trigger $window.prerenderReady once page is stable
// Note that since we have nested views - $viewContentLoaded is fired multiple
// times and we need to go around this problem
var viewContentLoads = 0;
var checkReady = function(previousContentLoads) {
var currentContentLoads = Number(viewContentLoads) + 0; // Create a local copy of the number of loads
if (previousContentLoads === currentContentLoads) { // Check if we are in a steady state
$window.prerenderReady = true; // Raise the flag saying we are ready
} else {
if ($window.prerenderReady || currentContentLoads > 20) return; // Runaway check
$timeout(function() {checkReady(currentContentLoads);}, 100); // Wait 100ms and recheck
}
};
$rootScope.$on('$stateChangeSuccess', function() {
checkReady(-1); // Changed the state - ready to listen for end of render
});
$rootScope.$on('$viewContentLoaded', function() {
viewContentLoads ++;
});
var myTestApp = angular.module("myTestApp", []);
myTestApp.controller("myTestController", function($scope, $window) {
$window.onload = function() {
alert("is called on page load.");
};
});
The solution that work for me is the following
app.directive('onFinishRender', ['$timeout', '$parse', function ($timeout, $parse) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('ngRepeatFinished');
if (!!attr.onFinishRender) {
$parse(attr.onFinishRender)(scope);
}
});
}
if (!!attr.onStartRender) {
if (scope.$first === true) {
$timeout(function () {
scope.$emit('ngRepeatStarted');
if (!!attr.onStartRender) {
$parse(attr.onStartRender)(scope);
}
});
}
}
}
}
}]);
Controller code is the following
$scope.crearTooltip = function () {
$('[data-toggle="popover"]').popover();
}
Html code is the following
<tr ng-repeat="item in $data" on-finish-render="crearTooltip()">
I use setInterval to wait for the content loaded. I hope this can help you to solve that problem.
var $audio = $('#audio');
var src = $audio.attr('src');
var a;
a = window.setInterval(function(){
src = $audio.attr('src');
if(src != undefined){
window.clearInterval(a);
$('audio').mediaelementplayer({
audioWidth: '100%'
});
}
}, 0);