I've got an app defined this way:
angular.module("myApp", [...])
.config(function ($stateProvider, $controllerProvider) {
if (isControllerDefined(controllerName)) {
do_stuff();
}
})
The controllers are defined this way:
angular.module("myApp")
.controller("myController", function ($scope) { ... });
How can I define isControllerDefined() (in the config above) to check whether a given controller exists if I have the name of the controller? I feel like I should be able to do something like one of these:
var ctrl = angular.module("myApp").getController("myController");
var ctrl = $controllerProvider.get("myController");
or something like that... but I can't find any functionality for this. Help?
An example of a service that can check if a controller exists. Note that it looks for a global function with specified name as well as a controller in the $controller provider.
angular.service('ControllerChecker', ['$controller', function($controller) {
return {
exists: function(controllerName) {
if(typeof window[controllerName] == 'function') {
return true;
}
try {
$controller(controllerName);
return true;
} catch (error) {
return !(error instanceof TypeError);
}
}
};
}]);
See the fiddle for usage.
I came across this exact same issue the other day. I had a few issues with the currently accepted answer, namely because one of my controllers was performing an initialization call out to the server upon instantiation to populate some data (i.e):
function ExampleController($scope, ExampleService) {
ExampleService.getData().then(function(data) {
$scope.foo = data.foo;
$scope.bar = data.bar
});
}
As it stands, the currently accepted answer will actually instantiate the controller, before discarding it. This lead to multiple API calls being made on each request (one to verify that the controller exists, one to actually use the controller).
I had a bit of a dig around in the $controller source code and found that there's an undocumented parameter you can pass in called later which will delay instantiation. It will, however, still run all of the checks to ensure that the controller exists, which is perfect!
angular.factory("util", [ "$controller", function($controller) {
return {
controllerExists: function(name) {
try {
// inject '$scope' as a dummy local variable
// and flag the $controller with 'later' to delay instantiation
$controller(name, { "$scope": {} }, true);
return true;
}
catch(ex) {
return false;
}
}
};
}]);
UPDATE: Probably a lot easier as a decorator:
angular.config(['$provide', function($provide) {
$provide.delegate('$controller', [ '$delegate', function($delegate) {
$delegate.exists = function(controllerName) {
try {
// inject '$scope' as a dummy local variable
// and flag the $controller with 'later' to delay instantiation
$delegate(controllerName, { '$scope': {} }, true);
return true;
}
catch(ex) {
return false;
}
};
return $delegate;
}]);
}]);
Then you can simply inject $controller and call exists(...)
function($controller) {
console.log($controller.exists('TestController') ? 'Exists' : 'Does not exist');
}
There is currently no easy way of fetching a list of controllers. That is hidden for internal use only. You would have to go to the source code and add a public method that return the internal controllers variable (in $ControllerProvider function)
https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L16
this.getControllers = function() {
return controllers;
// This will return an object of all the currently defined controllers
};
Then you can just do
app.config(function($controllerProvider) {
var myCtrl = $controllerProvider.getControllers()['myController'];
});
Since angular 1.5.1 (not released yet at the time of writing), there is a new way to check whether a controller exists or not through the $ControllerProvider.has('MyCtrlName') method.
Github issue: https://github.com/angular/angular.js/issues/13951
Github PR: https://github.com/angular/angular.js/pull/14109
Commit backported in 1.5.1 directly: https://github.com/angular/angular.js/commit/bb9575dbd3428176216355df7b2933d2a72783cd
Disclaimer: Since many people were interested by this feature, I made a PR, because I also need it is some of my projects. Have fun! :)
This PR has been based on #trekforever answer, thanks for the hint :)
You could use the $controller service and do $controller('myController') and wrap a try-catch arround it so you know if it fails.
Related
When using angularJS you can register a decorating function for a service by using the $provide.decorator('thatService',decoratorFn).
Upon creating the service instance the $injector will pass it (the service instance) to the registered decorating function and will use the function's result as the decorated service.
Now suppose that thatService uses thatOtherService which it has injected into it.
How I can I get a reference to thatOtherService so that I will be able to use it in .myNewMethodForThatService() that my decoratorFN wants to add to thatService?
It depends on the exact usecase - more info is needed for a definitive answer.
(Unless I've misunderstood the requirements) here are two alternatives:
1) Expose ThatOtherService from ThatService:
.service('ThatService', function ThatServiceService($log, ThatOtherService) {
this._somethingElseDoer = ThatOtherService;
this.doSomething = function doSomething() {
$log.log('[SERVICE-1]: Doing something first...');
ThatOtherService.doSomethingElse();
};
})
.config(function configProvide($provide) {
$provide.decorator('ThatService', function decorateThatService($delegate, $log) {
// Let's add a new method to `ThatService`
$delegate.doSomethingNew = function doSomethingNew() {
$log.log('[SERVICE-1]: Let\'s try something new...');
// We still need to do something else afterwards, so let's use
// `ThatService`'s dependency (which is exposed as `_somethingElseDoer`)
$delegate._somethingElseDoer.doSomethingElse();
};
return $delegate;
});
});
2) Inject ThatOtherService in the decorator function:
.service('ThatService', function ThatServiceService($log, ThatOtherService) {
this.doSomething = function doSomething() {
$log.log('[SERVICE-1]: Doing something first...');
ThatOtherService.doSomethingElse();
};
})
.config(function configProvide($provide) {
$provide.decorator('ThatService', function decorateThatService($delegate, $log, ThatOtherService) {
// Let's add a new method to `ThatService`
$delegate.doSomethingNew = function doSomethingNew() {
$log.log('[SERVICE-2]: Let\'s try something new...');
// We still need to do something else afterwatds, so let's use
// the injected `ThatOtherService`
ThatOtherService.doSomethingElse();
};
return $delegate;
});
});
You can see both approaches in action in this demo.
I can access $controllerProvider but can not access $controller in the following method
angular.module(MODULE_NAME, ['common'])
.config(['$routeProvider','$controllerProvider',
function($routeProvider, $controllerProvider) {
console.log($controllerProvider);//defined
console.log($controller);//undefined
}]);
If I use $controller as dependency injection, it is giving
Unknown provider: $controller
But I need to access it, How do I do that
EDIT
I need this because I want to check my controller exists not not. Here is the post where from I am using this code
try {
$controller(controllerName);
listControlerName = MODULE_NAME+'ListController';
} catch (error) {
listControlerName = 'CommonListController';
}
CONTEXT
I am creating architecture of a project. My project structure as follows.
I have one COMMON module. with ListController, EditController, ViewController
I have some other modules like MOD1, MOD2 etc with MOD1ListController, MOD1EditController, MOD1ViewController so on.
Those module specific controller extend corresponding controller from Common Module.
Now my plan is while a new module (MODX) need to be developed, then if there is some extra functionality, then only developer will create a new MODXListController for that module by inheriting common ListController. Otherwise they need not to create any thing.
So system will check if that module contains MODXListController or not. If not then system will use Common ListController.
I do not want to create a MODXListController which inherits common ListController but does not do any extra change. Because I have lots of module nearly 25 and all of then are sharing same functionality mostly.
Without explicitly inheriting all controllers (which is acceptable but verbose solution), a good alternative is to wrap controller switching functionality into mod-controller directive (similar to ng-controller), something like that:
angular.module('common', [])
.constant('MODULE_NAME', 'Common')
.constant('modControllers', [])
.controller('CommonEditController', ...);
.controller('CommonListController', ...);
.directive('modController', function (MODULE_NAME, modControllers) {
return {
restrict: 'A',
scope: true,
priority: 500,
controller: function ($controller, $attrs, $scope) {
var ctrlName = (modControllers.indexOf($attrs.modController) >=0 ? MODULE_NAME : 'Common') + $attrs.modController;
angular.extend(this, $controller(ctrlName, { $scope: $scope }));
});
};
});
angular.module('mod1', ['common'])
.constant('MODULE_NAME', 'Mod1')
.constant('modControllers', ['ListController']);
.controller('Mod1ListController', function ($controller) {
angular.extend(this, $controller('CommonListController');
...
});
When the controller should be specified in application code rather than view (i.e. route and directive controllers), the similar thing can be done with controller factory.
angular.module('common')
.provider('ctrlFactory', function (MODULE_NAME, modControllers) {
function factoryFn(ctrlName) {
return (modControllers.indexOf(ctrlName) >=0 ? MODULE_NAME : 'Common') + ctrlName;
};
this.get = this.$get = factoryFn;
});
It can be injected into directives and used as
...
controller: ctrlFactory('ListController')
Due to the fact that it returns controller name instead of $controller instance and depends only on constant services (MODULE_NAME, modControllers), service provider can also be used in the same way as service instance to declare route controllers in config block:
...
controller: ctrlFactoryProvider.get('ListController')
Because $controller doesn't expose the list of registered controllers, try-catching it comes to mind as automatic solution, and it is the last thing the one may want to do: besides it is a hack, it just suppresses possible exceptions and damages testability.
In .config you can only use providers (e.g. $routeProvider).
In .run you can only use instances of services (e.g. $route).
$controller is of type service which can not be used in .config.
For more detail on this read here.
The detailed discussion on Stack Overflow at this thread shows what can be injected into others.
You can not inject $controller into config
Although you can use the following service to check if your controller is defined.
angular.service('ControllerChecker', ['$controller', function($controller) {
return {
exists: function(controllerName) {
if(typeof window[controllerName] == 'function') {
return true;
}
try {
$controller(controllerName);
return true;
} catch (error) {
return !(error instanceof TypeError);
}
}
};
}]);
Now you can inject ControllerChecker and call its exists(CtrlName) function to check whether your controller is defined or not.
I am trying to set up a decorator for my controllers. My intention is to introduce some common behaviour across all the controllers in my app.
I have it configured to work in Angular 1.2.x, but there are some breaking changes from 1.3.x onwards that is breaking the code. The error one now gets is "controller is not a function".
Below is the code for the decorator:
angular.module('myApp', ['ng'], function($provide) {
$provide.decorator('$controller', function($delegate) {
return function(constructor, locals) {
//Custom behaviour code
return $delegate(constructor, locals);
}
})
});
Angular 1.2.x - http://jsfiddle.net/3v17w364/2/ (Working)
Angular 1.4.x - http://jsfiddle.net/tncquyxo/2/ (Broken)
In Angular 1.4.x modules have decorator method, $provide.decorator is no longer needed.
For monkey-patching APIs it is always preferable to use arguments instead of enumerating them explicitly, the chance that it will break is much lesser.
angular.module('myApp', ['ng']).decorator('$controller', function ($delegate) {
return function (constructor, locals) {
var controller = $delegate.apply(null, arguments);
return angular.extend(function () {
locals.$scope.common = ...;
return controller();
}, controller);
};
});
Answering my own question.
Had to dig in to Angular source code to figure out whats going on.
The $controller instance is created using below code. The fix lay in the parameter 'later'. This needs to be set to true.
return function(expression, locals, later, ident) {
// PRIVATE API:
// param `later` --- indicates that the controller's constructor is invoked at a later time.
// If true, $controller will allocate the object with the correct
// prototype chain, but will not invoke the controller until a returned
// callback is invoked.
Above taken from: https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js
Updated provider code:
angular.module('myApp', ['ng'], function($provide) {
$provide.decorator('$controller', function($delegate) {
return function(constructor, locals) {
//Custom behaviour code
return $delegate(constructor, locals, true);
}
})
});
Updated fiddle: http://jsfiddle.net/v3067u98/1/
I am working on an angularJS app and I am trying to stick with the most efficient and widely accepted styles of development in AngularJs.
Currently, I am using this way of declaring my services like so:
app.factory('MyService', function() {
/* ... */
function doSomething(){
console.log('I just did something');
}
function iAmNotVisible(){
console.log('I am not accessible from the outside');
}
/* ... */
return{
doSomething: doSomething
};
});
However, there are numerous examples out there and I am not quite sure which design style to follow. Can someone with extensive knowledge about services explain the reason why a certain style is more relevant than another?
Is what I am doing useful in any way other than restricting the access to certain functions in my service?
I would suggest you layout your factory or service as they do in the angular-seed app, except that app annoyingly only uses value in the services.js boilerplate. However you can adapt the layout they use for controllers, directives and filters.
'use strict';
/* Filters */
angular.module('myApp.filters', []).
filter('interpolate', ['version', function(version) {
return function(text) {
return String(text).replace(/\%VERSION\%/mg, version);
}
}]);
Which means for a service you would do:
'use strict';
/* Services */
angular.module('myApp.filters', []).
service('myservice', ['provider1', 'provider2', function(provider1, provider2) {
this.foo = function() {
return 'foo';
};
}]).
factory('myfactoryprovider', ['myservice', function(myservice) {
return "whatever";
}]);
This has more boilerplate than you absolutely need, but it is minification safe and keeps your namespaces clean.
Than all you have to do is decide which of const, value, factory or service is most appropriate. Use service if you want to create a single object: it gets called as a constructor so you just assign any methods to this and everything will share the same object. Use factory if you want full control over whether or not an object is created though it is still only called once. Use value to return a simple value, use const for values you can use within config.
I don't believe there's an accepted standard but I myself follow this convention:
var myServiceFn = function (rootScope, http) { ... };
...
app.factory('MyService', ['$rootScope', '$http', myServiceFn]);
I feel like this is cleaner than defining the function inline and also allows for proper injection if I ever decide to minify my files. (see http://docs.angularjs.org/tutorial/step_05).
As an example, I've been defining services within modules like so:
angular.module('userModule', [])
.service('userService', function() {
this.user = null;
this.getUser = function() {
return this.user;
};
this.userIsNull = function() {
return (this.user === null);
};
this.signout = function() {
this.user = null;
};
})
I think it is more clear to use the .service rather than the .factory?
I'm about to tear my eyes out over this really confusing issue, I'm trying to apply a simple decorator to the $route service but I keep getting an error saying that angular.module(...).decorator is not a function, which doesn't make sense because I've used this in other projects and it has worked just fine..
How can the decorator no longer be exposed on the angular.Module? Doing $provide.decorator doesn't work either because $provide isn't defined, but how am I supposed to inject it when the decorator is written outside of any other code..
What can be wrong here?
angular.module('core').decorator('$route', ['$delegate', function($delegate) {
$delegate.getRouteProp = function(path, prop) {
var result = null;
angular.forEach($delegate.routes, function(config, route) {
if (path === route) {
result = config[prop];
}
});
return result;
};
return $delegate;
}]);