9

I am using angular-bootstrap-colorpicker in my app and am having a weird issue. The colorpicker module has a factory named Slider. That's causing the colorpicker not to work because my app also has a factory called Slider. Refactoring every occurrence of this in the app isn't possible, and it seems like a sloppy workaround anyway. The error being thrown is

Uncaught TypeError: Slider.setSaturation is not a function

which I've concluded is because my app's factory has no method setSaturation and Angular is "confused". I don't really know enough about factories and how Angular organizes them, but it seems very odd that they would be available across modules like that. eg

angular.module('thomasApp', [])
...
.factory('Slider', ...

is affected by

angular.module('colorpicker.module', [])
...
.factory('Slider', ...

or vice versa.

Is there someway I can compartmentalize this colorpicker so that it does not interfere with my Slider factory?

Edit:

I agree with the linked answer that using a prefix as a namespace is a smart idea. However that would require an unrealistic amount of refactoring. I appreciate the answer below but it isn't fleshed out enough for me to be able to put into action.

1) Is this really the best possible solution (apart from prefixing from the project's beginning)? - If I make a change like this, will it be erased the next time I do a bower update, or someone pulls down my project and does a bower install?

2) Is there a better way? - If not, can the current answer be expanded and have explanations of what's happening added?

1252748
  • 14,597
  • 32
  • 109
  • 229
  • 1
    You have to rename your factory if you want to use that third-party module. I recommend using a prefix like "thomasSlider" or just "thSlider". You can also use periods like this "thomas.Slider" but you'll have to use array injections to pass it to a function. – Reactgular Oct 14 '15 at 15:51
  • @ThinkingMedia Is there really no way that I can simply rename parts of the third party module? – 1252748 Oct 14 '15 at 17:58
  • Possible duplicate of [Modules and namespace / name collision in AngularJS](http://stackoverflow.com/questions/13406791/modules-and-namespace-name-collision-in-angularjs) – fracz Oct 15 '15 at 08:31
  • 1
    I agree with the consensus that the way Angular handles namespacing isn't very good. However, can you clarify your comments "Refactoring every occurrence of this in the app isn't possible" / "[prefixing would require an] unrealistic amount of refactoring". My suspicion is that the refactoring would just be a search/replace job, which I think is quite a straightforward refactor, as refactors go... – Michal Charemza Oct 23 '15 at 07:52
  • Agree with @MichalCharemza; search & replace sounds like a quick and simple fix, as opposed to other possibilities. Why isn't it possible? Another (ugly) solution could be to create another angular application with color picker and access its service using [something like this answer suggests](http://stackoverflow.com/a/15536532/2066118). – Maciej Gurban Oct 26 '15 at 12:03
  • @thomas, you didn't mentioned anything, it would be really helpful if you can write what approach did you take to solve the problem. Cheers! – S.Klechkovski Oct 26 '15 at 15:28
  • @thomas, have tried my proposed solution below ? I quite believe it will solve your problem in the simplest way. Thanks. – marson parulian Oct 28 '15 at 13:13
  • @marsonparulian Sadly I haven't had a chance to get to try your solution yet. I've been bouncing between projects and will let you know as soon as I come back to this. Thank you so much for your help! – 1252748 Oct 28 '15 at 19:38

4 Answers4

7

The problem that you have is general already known issue in Angular. @fracz was right, it's connected with Modules and namespace / name collision in AngularJS. The issue is that Angular has only one $injector instance per module instantiatation and all defined injectable objects go into it. By injectable objects I mean constants, values, services, controllers, directives.. This is bad in cases as this one because even if we modularize our application by defining multiple modules and dependencies between them at the end all the defined injectable objects end up in a same context/namespace/injector.

Angular makes this even worse by not using fail-fast technique in such cases and because the application continues working you may end up noticing the issue late which often can lead to expensive refactorings. And there is still a question how we can improve this, IMO failing-fast at least will help in avoiding this issue at it's beginning.

However in your case you are lucky that the library is really small and what you need is only the color picker directive. I have made a workaround example here by defining the color picker directive in your module while taking it's definition from the instantiated library module $injector. Like this you are free to change even it's name :).

Code example:

// NOTE: redefine the directive
app.directive('colorpicker', function () {
  var injector = angular.injector(['ng', 'colorpicker.module']);
  return injector.get('colorpickerDirective')[0];
});

For clarification purposes there is also an example that shows how Angular behaves when you define two service with the same name. The application successfully continues working with the last service definition.

I hope that this answer makes the things clear and successfully solves your problem. If you have more questions feel free to ask. Cheers!

Community
  • 1
  • 1
S.Klechkovski
  • 4,005
  • 16
  • 27
5

Maybe something like this - create a fake module and wrap the existing provider under a new name. This will isolate the dependency.

var colorpicker = angular.module('my-colorpicker', ['colorpicker.module']);
colorpicker.factory('ColorPickerSlider', function() {
  var injector = angular.injector(['colorpicker.module']);
  var Slider = injector.get('Slider');
  return Slider;
});

I know that this doesn't solve the fundamental problem of namespaces but it gives a way of hiding existing dependencies in a sandbox.

FrailWords
  • 886
  • 7
  • 19
  • You still would have two `Slider` services. – a better oliver Oct 25 '15 at 06:49
  • @zeroflagL you are right. That's the reason I've mentioned in my answer that it doesn't answer the question of namespace conflict. I do wish that there is a simpler way of fixing this. – FrailWords Oct 25 '15 at 10:56
  • @zeroflagL Angular2 has 'useClass' to solve this problem - https://angular.io/docs/ts/latest/api/core/Provider-class.html but not sure how or if it can be ported to Angular 1.x. – FrailWords Oct 25 '15 at 11:20
0

Yes, you can handle multiple factories with same name.

Here is the example,

var app = angular.module('firstApp', []);
app.factory('SameFact', [function () {
    return { Name: "First" };
}]);

var app2 = angular.module('secondApp', ['firstApp']);

app2.factory('SameFact', [function () {
    return { Name: "Second" };
}]);

app2.controller("testController", ["SameFact", "$scope", "$injector", function (SameFact, $scope, $injector) {
    $scope.myName = {};
    $scope.myName.Name1 = SameFact.Name; // This will be "Second", Last factory
    var inj = angular.injector(['firstApp']);
    $scope.my_inject = inj.get('SameFact').Name; // This will be "First", the first factory
}]);

Note

When you pass a factory as a dependency to a controller, it will register the last registered factory.

That is, in this example, I have registered two factory with same name SameFact but in different module.

When I refer the factory SameFact from my controller it will always point to the factory which is registered last, ie, factory in secondApp.

But, you can manually refer the factory which is in the module firstApp.

You can use angular.injector(['module_name']) to select injector of your required module and then use get() function inside it to get your factory or service.

Conclusion

So, you need declare a scope variable which point to your Slide factory of colorpicker module. And use this scope variable to get all required operations within colorpicker module. Find where the place where you are calling functions of colorpicker and replace it with this new variable.

Hope this will help you to survive your current situation.

Feel free to ask any doubts regarding this !!!

Abhilash Augustine
  • 4,128
  • 1
  • 24
  • 24
0

Place colorpicker.module preceeding your module that's containing Slider factory, at your module initialization :

angular.module('thomasApp', ['colorpicker.module','anotherModule'])

Here is an example on js fiddle

marson parulian
  • 497
  • 3
  • 6