Optional dependencies in AngularJS

JavascriptAngularjsDependency InjectionOptional Parameters

Javascript Problem Overview


I'm trying to implement a controller in AngularJS which is used across multiple pages. It makes use of some services. Some of them are loaded on all pages, some - not. I mean it is defined in different files, and these files are loaded independently. But if I do not load these services on all pages I got error:

Error: Unknown provider: firstOtionalServiceProvider <- firstOtionalService

So, I need to load scripts on all pages. Can I declare dependency as optional in Angular? E.g:

myApp.controller('MyController', ['$scope', 'firstRequiredService', 'secondRequiredService', 'optional:firstOptionalService', 'optional:secondOptionalService', function($scope, firstRequiredService, secondRequiredService, firstOptionalService, secondOptionalSerivce){

    // No need to check, as firstRequiredService must not be null
    firstRequiredService.alwaysDefined();

    // If the dependency is not resolved i want Angular to set null as argument and check
    if (firstOptionalService) {
        firstOptionalService.mayBeUndefinedSoCheckNull();
    }

}]);

Javascript Solutions


Solution 1 - Javascript

Apparently not using automatic injection. However, you can inject the injector and check for the service:

myApp.controller('MyController', [
    '$scope', '$injector', 'firstRequiredService', 'secondRequiredService', 
    function ($scope, $injector, firstRequiredService, secondRequiredService) {
        if ($injector.has('firstOptionalService')) {
            var firstOptionalService = $injector.get('firstOptionalService');
        }
    }
]);

Solution 2 - Javascript

No, Angular does not yet support optional dependencies out of the box. You'd better put all your dependencies into a module and load it as one Javascript file. If you need another set of dependencies - consider creating another module in another JS and putting all common dependencies to common JS.

However, behavior you've described can be achieved with $injector service. You simply inject $injector instead of all your dependencies to a controller and pull dependencies from it manually, checking if they exist. That's it:

index.html:

<!DOCTYPE html>
<html data-ng-app="myApp">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
    <script src="app.js"></script>
    <script src="1.js"></script>
    <script src="2.js"></script>
    <title>1</title>
  </head>
  <body data-ng-controller="DemoController">
  </body>
</html>

app.js:

var myApp = angular.module('myApp', []);

myApp.service('commonService', function(){
	this.action = function(){
		console.log('Common service is loaded');
	}
});

myApp.controller('DemoController', ['$scope', '$injector', function($scope, $injector){
	var common;
	var first;
	var second;

	try{
		common = $injector.get('commonService');
		console.log('Injector has common service!');
	}catch(e){
		console.log('Injector does not have common service!');
	}
	try{
		first = $injector.get('firstService');
		console.log('Injector has first service!');
	}catch(e){
		console.log('Injector does not have first service!');
	}
	try{
		second = $injector.get('secondService');
		console.log('Injector has second service!');
	}catch(e){
		console.log('Injector does not have second service!');
	}

	if(common){
		common.action();
	}
	if(first){
		first.action();
	}
	if(second){
		second.action();
	}
}]);

1.js:

myApp.service('firstService', function(){
	this.action = function(){
		console.log('First service is loaded');
	}
});

2.js:

myApp.service('secondService', function(){
	this.action = function(){
		console.log('Second service is loaded');
	}
});

See it live in this plunk! Try to play with <script> tags and watch for console output.

P.S. And, as @Problematic said, you can use $injector.has(), starting from AngularJS 1.1.5.

Solution 3 - Javascript

I'd probably go with @Proplematic's suggestion of using $injector. However, there is another solution I can think of: register all services with their default values (null for example) in your bootstrap file. When additional files are loaded, later definitions will override the default definitions, somewhat creating the effect you desire.

var app = angular.module('plunker', []);

app.value("service1", null)
   .value("service2", null)
   .factory("service1", function() { return "hello"; });

app.controller('MainCtrl', function($scope, service1, service2) {
  console.log(service1); // hello
  console.log(service2); // null
});

Demo link

Solution 4 - Javascript

This is how i solved it:

var deps = [];

try {
    //Check if optionalModule is available
    angular.module('app').requires.push('optionalModule');
    deps.push('optionalModule');
} catch(e){
    console.log("Warn: module optionalModule not found. Maybe it's normal");
}

angular.module('app', deps).factory('stuff', function($injector) {
    var optionalService;

    if($injector.has('optionalService')) {
        optionalService = $injector.get('optionalService');
    } else {
        console.log('No waffles for you, dear sir');
    }
});

Solution 5 - Javascript

Try this way..

try {
    angular.module('YourModule').requires.push('Optional dependency module');
} catch(e) {
    console.log(e)
}
    

'requires' is an Array of dependency modules.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmolacchaView Question on Stackoverflow
Solution 1 - JavascriptDerek StobbeView Answer on Stackoverflow
Solution 2 - JavascriptmadheadView Answer on Stackoverflow
Solution 3 - JavascriptBuuView Answer on Stackoverflow
Solution 4 - JavascriptKenneth LynneView Answer on Stackoverflow
Solution 5 - JavascriptSimbuView Answer on Stackoverflow