I'm not sure why, but when I catch an event from JQuery, update the route and notify Angular of the change, it goes into an infinite route. It's easy to reproduce, say you have a screen like this:
The click of the button is handled by jQuery, because the jquery library doesn't seem to work in a directive so I put it in a service. Once the controller loads I initialize this. Then when the user clicks that button, I catch this jquery event, and raise a new event on the root scope so that I know to navigate to the new view. Here's the code:
(function(angular, $) {
var app = angular.module('app', ['ngRoute']);
app.config(['$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$routeProvider.when('/', {
templateUrl: 'list.html',
controller: 'ListController'
});
// The view I am trying to reach via jquery event.
$routeProvider.when('/Edit', {
templateUrl: 'edit.html',
controller: 'ListController'
});
$locationProvider.html5Mode(true);
}]);
app.controller('ListController',
function ListController($scope, $rootScope, $location, JQuerySerivce) {
// Initialize the button click listener.
JQuerySerivce.registerBtn();
$rootScope.$on('clicked', function() {
$location.path("./Edit");
// Notify angular stuff happened.
$scope.$apply();
});
});
app.controller('EditController', function EditController() {
console.log("Edit view loaded!");
});
app.factory("JQuerySerivce", function ($rootScope){
return {
registerBtn: function() {
$('#jqueryBtn').on('click', function() {
$rootScope.$broadcast('clicked');
});
}
};
});
})(angular, jQuery);
The working plunker example:
https://embed.plnkr.co/WIwRreYtofGKQWdjTeQx/
[EDIT]
To clarify my question, how can I prevent this infinite loop from happening? All of the other quesitons I've found concerning routing and infinite loops have usually been some sort of mistake that introduces a loop. In this case I'm just navigating and the framework is dying. Maybe my approach is wrong? I'm open to changing it if anyone has an alternative. I can't escape the jquery event though :/ Any help would be much appreciated.
$location.path("/Edit");
instead of
$location.path("./Edit");
?
Related
I have a controller that has an event called changeSafe. I want other controllers to listen when this event gets fired
secure.controller('wrapperCtrl', function ($scope, $rootScope, storeFactory, safeFactory) {
$scope.changeSafe = function (safeId) {
$scope.loading = true;
safeFactory.setSafe(safeId).then(function (safeData) {
$scope.safe = safeData;
$scope.loading = false;
$rootScope.$broadcast('changeSafe', safeData);
});
}
});
The first page that loads is called dashboard when I add what is below the page re-draws with $scope.safe as I would expect it to.
secure.controller('dashboardCtrl', function ($scope, $rootScope, storeFactory, safeFactory) {
$scope.$on('changeSafe', function (event, arg) {
bindSafe(arg.safeId);
});
});
I have pretty much the exact same thing on my history Controller
secure.controller('historyCtrl', function($scope, $rootScope, storeFactory, safeFactory) {
$scope.$on('changeSafe', function (event, arg) {
bindHistory(arg.safeId);
});
});
Here is I have in the config section
secure.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/history', {
templateUrl: '/Angular/History/history.html',
controller: 'historyCtrl'
})
.otherwise({
templateUrl: '/Angular/Dashboard/dashboard.html',
controller: 'dashboardCtrl'
});
}]);
Whenever I click the button that is held within wrapperCtrl only the $scope.$on fires in the dashboardCtrl. Can anyone see why the $scope.$on is not being fired from the historyCtrl controller? I'm also unclear why it gets called from dashboardCtrl when I'm not on that view anymore.
When I step through the code, I'm actually seeing the $rootScope.$on('changeSafe') getting called multiple times both on history page and dashboard page. I can't figure out why it is changing views back to the dashboard though
I'm not sure if I have a full grasp of your problem, but my best guess is you are having a load order issue and the event is being broadcasted before the subscription has been initialized.
First off, to save yourself a lot of headaches, it is important to have a solid understanding of Event Handling in AnguarJS:
know the difference between subscribing on $rootScope.$on vs
$scope.$on
know the difference of publishing with $broadcast vs $emit on
$rootScope vs $scope
From the comments it sounds like you might have been using $rootScope.$on, which isn't cleaned up when your controllers are destroyed (same as directives):
var changeSafeListnerUnbindFunction = $rootScope.$on('changeSafe', funciton() { //... });
$scope.$on('$destroy', changeSafeListnerUnbindFunction);
Given your use case, registering listeners on the child scopes will pick up events published from the $rootScope.$broadcast (which communicates top level down to each child scope).
You probably have a load order issue?
I have a situation where the Angular $routeProvider appears to not fire controller actions on route changes.
The routes are super simple urls:
window.app = angular.module('app', ['ngRoute', 'app.filters', 'app.services', 'app.directives', 'app.controllers'])
.config([
'$routeProvider', function($routeProvider) {
console.log("app.js config launched");
$routeProvider
.when('/nav', {
templateUrl: 'temp/test.html',
controller: 'navController'
// controller: function($scope) { alert('scope called.') }
})
.when('/home', {
controller: 'homeController',
template: ' '
});
$routeProvider.otherwise({ redirectTo: '/home' });
}
]);
The controller is just an log out to verify access:
app.controller('navController', [
"$scope", "cellService",
function ($scope, cellService) {
console.log("**** navController fired");
}
]);
The initialization code fires so the routing is initialized. When I hit:
http://localhost:4333/app/#/nav
and the url changes I can see that the test.html template is accessed by the browser, but the controller never fires.
This seems to indicate the route is getting activated by the URL change, but for some reason the controller is not firing. I've tried using a function instead of a controller name, but that too never gets fired. I've also verified that the controller is valid by attaching ng-controller="navController" to an element and that fires the controller just fine.
This is a page that originally didn't have routing associated as it was basically single self-contained page that didn't need navigation. I added the route code after the fact. I added an ng-view (there wasn't one before) after which at least the template started loading - without ng-view nothing happens.
Stumped and not sure what else to look at. Help.
It turns out the problem really was operator error on my part, but I think it's a scenario that can cause issues so I'll use this as the answer.
The issue that caused this problem were two-fold:
The HTML template HTML page (via templateUrl) had an invalid URL so the page never loaded
and the controller wasn't fired because of that.
When switching to a template I used an empty template (" ") but had also
removed the ng-View directive. The ng-View directive MUST BE present
even when using an empty template. Without it the controller doesn't fire.
In both cases it didn't work and I mistakenly assumed that the controller was not getting fired which was confusing because it did fire if I explicitly hooked it up with ng-controller.
Yup plain operator error, but the latter is vitally important - without ng-View the controller doesn't fire.
What happens if you define the function externally and reference that? So instead of
.when('/nav', {
templateUrl: 'temp/test.html',
controller: 'navController'
})
It would be
.when('/nav', {
templateUrl: 'temp/test.html',
controller: navController
})
and elsewhere
function navController($scope, cellService){
console.log("**** navController fired");
}
navController.$inject = ['$scope', 'cellService'];
I am using route provider as follows,
var appModule = angular.module('ngLogin', ['ngRoute','restangular','btford.socket-io','ngSanitize','xeditable']);
appModule.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'sample/homepage.html',
controller: 'ngHomeControl'
}).
when('/contacts', {
templateUrl: 'sample/homepage.html',
controller: 'ngContactControl'
});
}]);
Here I need to call function from ngHomeControl to ngContactControl.
I tried as follows, but the function didn't invoked.
appModule.controller('ngHomeControl', function($scope,$routeParams,socket,Restangular,$http) {
$rootScope.$broadcast('getFriendList',{"userName":userName});
});
appModule.controller('ngContactControl', function($scope,$routeParams,$rootScope,socket,sharedProperties,Restangular,$http,$timeout) {
$scope.$on("getFriendList",function(event,data)
{
console.log('getFriendList');
});
});
Can anyone help me to resolve?
This will not work as only one controller is instantiated at a time (in your case).
A proper way would be to use a service. There is a nice article that wil help you with this.
See also this answer on how to create a service.
Based on those two resources you should came up with something similar to this:
var appModule = angular.module('appModule', ['ngRoute']);
appModule.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'home.html',
controller: 'ngHomeControl'
}).
when('/contacts', {
templateUrl: 'contacts.html',
controller: 'ngContactControl'
});
}]);
appModule.service('friendsService', function(){
this.getFriendList = function () {
return ['John', 'James', 'Jake'];
}
});
appModule.controller('ngHomeControl', function($scope, friendsService) {
$scope.homeFriends = friendsService.getFriendList();
});
appModule.controller('ngContactControl', function($scope, friendsService) {
$scope.contactFriends = friendsService.getFriendList();
});
There is a complete working JSFiddle so you can test it out.
Please also checkout the console output to see when used components are instantiated.
You will see that controllers are instantiated each time the route changes - they are instantiated implicitly via the ngController directive used inside templates. The service is instantiated only once and this is at the time when it is needed/injected for the first time.
The reason your event listener isn't fired is because there isn't a ngContactControl instance alive when your at /home. You can create a parent controller, which handles the scope events but a better way is to use a service that is shared among the controllers that need this functionality.
See this plunker for an example how to share data and/or functions via a service.
I am new to angular js and currently stuck with very wired kind of a bug. function in a controllers runs twice when its called by view loaded against a route.
http://jsfiddle.net/4gwG3/5/
you will see alert twice!!
my view is simple
and my app code is following
var IB = angular.module('IB', []);
//channel controller
IB.controller('channelsController', function ($scope, $routeParams) {
$scope.greet = function () {
alert('hi');
};
});
IB.config(function ($routeProvider) {
$routeProvider
.when('/channels', {
controller: 'channelsController',
template: '{{greet()}}'
})
.otherwise({ redirectTo: '/channels' });
});
First check that you're not initializing your Angular app twice (by having it initialized automatically with ng-app).
One time I had 2 html pages with ng-app (one for login.html and
another for main.html) and this was a problem I realized later.
Second and for me the most important, check if you have attached your controller to multiple elements. This is a common case if you are using routing.
In my case I was navigating to DashboardController like so:
app.config(function($routeProvider){
$routeProvider
.when('/', {
controller: 'DashboardController',
templateUrl: 'pages/dashboard.html'
})
});
But I also had this in dashboard.html:
<section class="content" ng-controller="DashboardController">
Which was instructing AngularJS to digest my controller twice.
To solve it you have two ways:
removing ng-controller from your html file like this:
<section class="content">
or removing controller from routing (that is normally situated in app.js):
app.config(function($routeProvider){
$routeProvider
.when('/', {
templateUrl: 'pages/dashboard.html'
})
});
I think by creating an interpolation {{greet()}}, you create a watch on function greet. This function can get call as many time as digest cycle runs, so it is not a question about it running 1 or 2 times. So you should not depend upon the times the function is called.
I dont know what you are trying to achieve here. There are two alerts
1. When the controller is called.
2. When the template is get evaluated.
template is to provide the view part, however, in this case template is just evaluating function which is not creating any view.
I had the same problem, so I did:
$scope.init=function()
{
if ($rootScope.shopInit==true) return;
$rootScope.shopInit=true;
...
}
$scope.init();
Like if it were a singleton ! (I had many ajax calls each time I display, it was boring)
I have faced some problem trying to setup angular routing.
I have an application that is bootstrapped by hand (I need this feature, because I already have html5 navigation throughout whole site, and want angular to work only on certain page - it works fine).
But when I run my code I got some issues:
if .otherwise provided, I got infinite loop calling my controller and dying with Range error in Chrome
and I always got url redirection from hash version to non hash, even if I call $locationProvider.html5Mode(false);
Guess issues may be caused by fact that I am already using history.js and event handlers for statechange events, but for now I'm stuck with no clues.
Need your help to got some answer.
Thanks for your time.
And code.
HTML
<script>
if (typeof angular === 'undefined')
{
Modernizr.load({
load: [
'/static/css/angular.css',
'/static/js/libs/angular/angular.min.js',
'/static/js/files/angular/app.js',
'/static/js/files/angular/controllers.js',
'/static/js/files/angular/filters.js',
'/static/js/files/angular/services.js',
'/static/js/libs/angular/angular-resource.min.js',
],
complete: function () {
angular.bootstrap(document.getElementById('manager'), ['manager']);
}
});
} else {
angular.element(document).ready(function() {
angular.bootstrap(document.getElementById('manager'), ['manager']);
});
}
</script>
app.js
/* App Module */
var FM = angular.module('manager', ['managerFilters'])
.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider
.when('/home', { controller: FilesListCtrl, templateUrl: '/static/partials/1.html' })
.otherwise({ redirectTo: '/home' });
}]);
controllers.js
function FilesListCtrl($scope, $filter, $routeParams) {
app.log('FilesListCtrl')
}
#blesh #Flek thank you.
It was history.js to blame. When you try to use angular routing with it you'll have no option to set
$locationProvider.html5Mode to false. And other bugs occur like infinite redirection with default route provided.