I have the following code under my AngularJS .run which works perfectly on my local development machine but won't work when uploaded to my client server...after few tests it is obvious that by the time the controller is loaded the event is not triggered yet so most of the functions in the controller depending on this event do not work. Can someone please tell me what I am doing wrong here and how to fix it? Thanks
myApp.run(['AuthDataSvc', '$rootScope', function (AuthDataSvc, $rootScope) {
AuthDataSvc.getAuth().then(function(Token){
$rootScope.$broadcast('Token', Token);
}, function(status){
console.log(status);
});
}]);
You are always going to have a race condition. I have a couple alternatives you can do:
1) Use a service. I am not really a fan of this option because it leads to Spaghetti code. And most time you don't want the controller to run until you have logged on. I prefer option 2.
myApp.run(['AuthDataSvc', '$rootScope', function (AuthDataSvc, $rootScope) {
AuthDataSvc.getAuth(); /* no op we will use the service to determine logged in */
}]);
/* inside a controller */
if(AuthDataSvc.isLoggedIn()){
//do something.
}
2) Use a route.resolve. Resolves are defined on the route and the Controller will only load once it the promise has been set to resolved. I showed you an example for ui-router and ng-route you need to pick your poison. If you dont use ui-router you should consider it.
/* app.config ... route config.. */
var waitForLogon = {
UserToken: ["AuthDataSvc", function (AuthDataSvc) {
return AuthDataSvc.logon();
}]
};
//this is for ng-route
$routeProvider
.when('/Book/:bookId', {
templateUrl: '--',
controller: 'MyCtrl',
resolve: waitForLogon
})
//this is for ui-router
$stateProvider
.state('me', {
templateUrl: '--',
controller: 'MeCtrl',
resolve: waitForLogon
})
/* controller */
angular.module('yourapp')
.controller('MyCtrl', ["UserToken", ... , function(UserToken){
//User Token will always be here when your Ctrl loads.
});
/* service code -- */
angular.module('yourapp')
.service('AuthDataSvc', ["LogonModel", "$q", function(LogonModel, $q) {
this._q = null;
var that = this;
this._doAuth = function(){
this.getAuth().then(function(Token){ that._q.resolve(Token) }, function(error){that._q.reject(error);}
};
this.logon = function () {
if(!this._q){
this._q = $q.defer();
this._doAuth();// <-current auth do here, and resolve this._q when done
}
return this._q.promise;
};
});
Related
I wrote some custom routing in my app configuration file to handle routing of all my html templates and controllers instead of having to specifically define the route for every single html and controller. I have a Registration.html and RegistrationController.js under my Modules/Account/ directory. My app can find the controller the first time I go to it and I can fill out the page and submit the form on the page. After I submit successfully, I get redirected to a success page. When I try to go back to the same registration html/controller the 2nd time, it can find my html template, but it can not find my controller anymore and i get the error "Argument 'RegistrationController' is not a function, got undefined". Can anyone tell me why and how to fix this?
Please note this error only happens after a form submit. If I leave the page and go back to it without doing a form submit, everything works fine.
App Config
define(['angularAMD', 'angular-route', 'ui-bootstrap', 'ui-grid'], function (angularAMD) {
var app = angular.module("MyApp", ['ngRoute', 'ui.bootstrap', 'ui.grid']);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider
.when("/", angularAMD.route({
templateUrl: function (rp) { return 'Modules/Account/login.html'; },
controllerUrl: 'Modules/Account/LoginController'
}))
.when("/:module/:page", angularAMD.route({
templateUrl: function (rp) { return 'Modules/' + rp.module + '/' + rp.page + '.html'; },
resolve: {
load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {
var path = $location.path();
var parsePath = path.split('/');
var parentPath = parsePath[1];
var controllerName = parsePath[2];
var loadController = "Modules/" + parentPath + "/" + controllerName + "Controller";
debugger;
var deferred = $q.defer();
require([loadController], function () {
$rootScope.$apply(function () {
deferred.resolve();
});
});
return deferred.promise;
}]
}
}))
.otherwise({ redirectTo: '/' });
}]);
loadDirectives(app);
angularAMD.bootstrap(app);
return app;
});
RegistrationController
define(['app-config','accountService'], function (app) {
app.register.controller('RegistrationController', ['$scope', '$rootScope', '$location', '$uibModal', 'accountService',
function ($scope, $rootScope, $location, $uibModal, accountService) {
$rootScope.applicationModule = "Account";
$scope.registerUserSuccess = function (response, status) {
debugger;
$location.path("/Account/RegistrationSuccess");
}
$scope.registerUserFailure = function (response, status) {
if (!response.ValidationErrors) {
$scope.ErrorList = [];
$scope.ErrorList.push("An error occurred. Please contact the system's administrator");
}
else {
$scope.ErrorList = response.ValidationErrors;
}
}
$scope.onSubmitClick = function (isValid) {
if (isValid) {
accountService.registerUser($scope.regModel, $scope.registerUserSuccess, $scope.registerUserFailure);
}
}
$scope.onCancelClick = function () {
$location.path("/Login");
}
}
]);
});
I haven't honed in on the answer yet, but I've noticed some refactoring that you should do that might get you closer to figuring out your problem ...
Stop using controllers. Use components, which were introduced in 1.5. These are superior in their reusability. They are more flexible in how you can use them and in what you can pass into them. The only controllers you should be using are the ones in components.
Stop using $scope or $rootScope for anything. Used named controllers. This is the default setting for a component.
Consider ui-router over angular-route. It's just a lot better.
What is loadDirectives(app) doing?
Put a log statement in your RegistrationContoller.js and just verify that it isn't being called more than once. If it thinks RegistrationController is undefined after previously being defined, it just feels like it's being defined more than once.
Is there anything fishy in accountService.registerUser? Is this function forwarding you to the success screen? That's kind of weird... seems to me that the accountService.registerUser should return a promise, and the onSubmitClick should resolve the promise and forward the user.
Try forwarding back to the RegistrationController at different points in the code, and try to narrow down the exact point that it becomes undefined. I think that you have some code running somewhere that you don't think you do.
Trying to access a service in the resolve:
angular
.module('app', ['ui.router', 'templates'])
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/views/home.html',
controller: 'Home as home',
resolve: {
productIndex: function (ProductService) {
debugger;
// return ProductService.getProductsIndex();
}
}
});
$urlRouterProvider.otherwise('/');
});
When the code above is run, the debugger never hits and nothing comes-up in the console. When ProductService is removed as a parameter, the debugger hits, but clearly, the service can't be called. If possible I would also prefer to only make http calls from services.
I've been looking around for a while now, and only see working examples of similar injections. Even looking back at previous (working) projects, I can't see any difference with what was done above. My guess is that something might be wrong somewhere else. Any help would be great!
For the sake of completeness:
function ProductService($http) {
this.getProductsIndex = function() {
// debugger;
// return $http.get('/products');
};
}
angular
.module('app')
.controller('ProductService', ProductService);
Right now your services is being registered as a controller. you should register it as a service instead. e.g.
angular
.module('app')
.service('ProductService', ProductService);
var app = angular.module('*****')
app.service('ProductService', function() {
return {
getData: function($q, $http) {
var defer = $q.defer();
$http.get('****').success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
};
});
abou resolves
resolve takes either the string of a service or a function returning a value to be injected
resolve: {
productIndex: function (ProductService) {
// your stuff goes here.
}
}
this shouls work.. :)
I'm writing my first AngularJS application. The landing page has two links #/view1 and #/view2. Every link will call individual controller to render <div ng-view></div> individually.
The view1Ctrl will fetch data from server periodically. I can see ajax call in console every X seconds. Now I click #/view2, the app should use view2Ctrl. I expect view1Ctrl should no longer fetch data from the server. But actually it does.
.controller('View1Ctrl', function($scope, $http) {
$scope.update = function() {
$http.get('/api/foo')
.success(function(data) { /* got new data */ })
.error(function(data) { /* error occurred */ })
.finally(function() {
setTimeout($scope.update, 1000);
});
}
$scope.update();
});
I've two questions:
Is a controller always active after it is initialized.
What is the best practice to stop any background controllers? My idea is to check the $location.path().
Update 1:
The controller is actually destroyed. But the operation update will invoke it by itself, so the operation will be called endless.
My current workaround will check whether the current location has been change. So it works. I'm looking for a more elegant way, if it exists.
.controller('View1Ctrl', function($scope, $http) {
$scope.update = function(path) {
$http.get('/api/foo')
.success(function(data) { /* got new data */ })
.error(function(data) { /* error occurred */ })
.finally(function() {
if (path === $location.path()) {
setTimeout($scope.update, 1000, path);
}
});
}
$scope.update($location.path());
});
Update 2:
A better way is to watch destruction and cancel an ongoing timeout promise. Thanks #pankajparkar
.controller('View1Ctrl', function($scope, $http, $timeout) {
var timeoutPromise;
$scope.$on("$destroy", function(){
$timeout.cancel(timeoutPromise);
});
$scope.update = function() {
$http.get('/api/foo')
.success(function(data) { /* got new data */ })
.error(function(data) { /* error occurred */ })
.finally(function() {
timeoutPromise = $timeout($scope.update, 1000);
});
}
$scope.update();
});
I think you are mentioned ng-controller on the body tag or the parent of your ng-view, In your case you should load the controller from your $routeProvider that will handle it,
Config
app.config(function($routeProvider) {
$routeProvider
.when('/view1', {
templateUrl: 'view1.html',
controller: 'view1Ctrl' //load specific controller from here
})
.when('/view2', {
templateUrl: 'view2.html',
controller: 'view2Ctrl' //load specific controller from here
})
.otherwise({
redirectTo: '/view1'
});
});
HTML
<div class="wizard">
<ng-view></ng-view>
</div>
Below are the answer of your questions.
Now when $routeProvider load view1.html the view1Ctrl will exists, as soon as user navigates to view2.html the view1Ctrl will stop its working view2Ctrl will comes into a picture as you mentioned that inside your route configuration.
You should always use $routeProvider or $stateProvider(angular-ui-router) to set up routing inside you application, and load templates and controller on basis of routing. No need to check $location.path()
Working Plunkr
Update
Your problem was with the repetitive function call, which was running in background even after controller scope has vanished from DOM.
The best way to clear out this function while redirecting to other controller.
I'd suggest you to put your $timeout promise(you used setTimeout changed it to $timeout) in one java script variable, and cancel that while leaving(unregistered the controller). While leaving controller $destroy event gets dispatched by angular, which is act like destruct-or of controller, you can use of it, you could cancel your $timeout promise inside that.
CODE
var timeoutPromise = setTimeout($scope.update, 1000);
$scope.$on("$destroy", function(){
$timeout.cancel(timeoutPromise);
});
Plunkr with canceling promise of $timeout.
Reference Link
Hope this could help you, Thanks.
I'm trying to get to work angular.js, ui-router, and require.js and feel quite confused. I tried to follow this tutorial http://ify.io/lazy-loading-in-angularjs/. First, let me show you my code:
app.js =>
var app = angular.module('myApp', []);
app.config(function ($stateProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
$stateProvider.state('home',
{
templateUrl: 'tmpl/home-template.html',
url: '/',
controller: 'registration'
resolve: {
deps: function ($q, $rootScope) {
var deferred = $q.defer(),
dependencies = ["registration"];
require(dependencies, function () {
$rootScope.$apply(function () {
deferred.resolve();
});
})
return deferred.$promise;
}
}
}
);
app.lazy = {
controller: $controllerProvider.register,
directive: $compileProvider.directive,
filter: $filterProvider.register,
factory: $provide.factory,
service: $provide.service
};
});
Now in my registration.js I have following code:
define(["app"], function (app) {
app.lazy.controller("registration" , ["$scope", function ($scope) {
// The code here never runs
$scope.message = "hello world!";
}]);
});
everything works well, even the code in registration.js is run. but the problem is code inside controller function is never run and I get the error
Error: [ng:areq] http://errors.angularjs.org/1.2.23/ng/areq?p0=registration&p1=not a function, got undefined
Which seems my code does not register controller function successfully. Any Ideas?
P.s. In ui-router docs it is said "If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $routeChangeSuccess event is fired." But if I put the deferred.resolve(); from mentioned code inside a timeOut and run it after say 5 seconds, my controller code is run and my view is rendered before resolve, Strange.
Seems like I ran into the exact same problem, following the exact same tutorial that you did, but using ui-router. The solution for me was to:
Make sure the app.controllerProvider was available to lazy controller script. It looked like you did this using app.lazy {...}, which a really nice touch BTW :)
Make sure the lazy ctrl script uses define() and not require() I couldn't tell from your code if you had done this.
Here is my ui-router setup with the public app.controllerProvider method:
app.config(function ($stateProvider, $controllerProvider, $filterProvider, $provide, $urlRouterProvider) {
app.lazy = {
controller: $controllerProvider.register,
directive: $compileProvider.directive,
filter: $filterProvider.register,
factory: $provide.factory,
service: $provide.service
};
$urlRouterProvider.otherwise('/');
$stateProvider
.state('app', {
url:'/',
})
.state('app.view-a', {
views: {
'page#': {
templateUrl: 'view-a.tmpl.html',
controller: 'ViewACtrl',
resolve: {
deps: function ($q, $rootScope) {
var deferred = $q.defer();
var dependencies = [
'view-a.ctrl',
];
require(dependencies, function() {
$rootScope.$apply(function() {
deferred.resolve();
});
});
return deferred.promise;
}
}
}
}
});
});
Then in my lazy loaded controller I noticed that I had to use require(['app']), like this:
define(['app'], function (app) {
return app.lazy.controller('ViewACtrl', function($scope){
$scope.somethingcool = 'Cool!';
});
});
Source on GitHub: https://github.com/F1LT3R/angular-lazy-load
Demo on Plunker: http://plnkr.co/edit/XU7MIXGAnU3kd6CITWE7
Changing you're state's url to '' should do the trick. '' is root example.com/, '/' is example.com/#/.
I came to give my 2 cents. I saw you already resolved it, I just want to add a comment if someone else have a similar problem.
I was having a very similar issue, but I had part of my code waiting for the DOM to load, so I just called it directly (not using the "$(document).ready") and it worked.
$(document).ready(function() { /*function was being called here*/ });
And that solved my issue. Probably a different situation tho but I was having the same error.
Redirecting is not functional in the below code. My console.log() works fine, but the URL doesn't change. Needless to say, I'm confused as to why. I've changed the URL like this is another section of my application and it works great.
angular.module('workstation.process.approval', [
/* Dependencies */
'workstation.services'
]).
config(['$routeProvider', function($routeProvider) {
/* URL mappings */
$routeProvider.
when('/approval', {templateUrl: 'partials/loading.htm', controller: 'ApprovalCtrl'}).
when('/approval/dashboard', {templateUrl: 'partials/approval/dashboard.htm', controller: 'ApprovalCtrl'})
}]).
controller('ApprovalCtrl', ['$scope', '$cookieStore', 'WorkflowProcessService', 'socketUrl', '$location', function ($scope, $cookieStore, WorkflowProcessService, socketUrl, $location) {
var socket = portal.open(socketUrl+'/socket/workstation/approval');
socket.on({
open: function () {
this.send('getDashboard', {
workstationUuid: $cookieStore.get('workstationUuid')
}, function(r) {
$scope.dashboard = r;
console.log('swap-path', r);
$location.path('/approval/dashboard'); //NOT WORKING
});
},
close: function (reason) {
console.log(reason);
}
});
}]);
Try adding $scope.$apply() after you change the path. The socket on callback is called outside Angular, so you may need to tell Angular to run a digest cycle to notice the change. – Mark Rajcok May 14 at 21:18
it works for me thanks
I think, window.location() would suit your needs.
Just add
$window as a dependency
regards