how angular $inject is working in background - javascript

i know how to use $inject in my project.
angular.module("myApp",["ngRouter"]);
angualr.module("myApp").controller("myCtrl",myCtrl);
myCtrl.$inject=["$scope"];
function myCtrl(sc){
sc.a=10;
sc.b=20;
}
my question is how myCtrl is able to have $inject as property and take array of injectables.As per my understanding one can do
Function.prototype.$inject=[]; //assigning $inject to Function prototypes
//then we can use
function a(){
this.a=10;this.b=20}
a.$inject=[1,2,3]
if i am right till here then please explain what might be next steps?
If i am wrong please correct me.

if you want to expose the $injector outside of angular (but after the angular's bootstrap has been executed) you can do the following:
angular.module("myApp").run(['$injector', function($injector){
angular.$$injector = $injector;
}]);
Keep in mind that this is an hack, and if you want to use angular you shoud write your code in the angular's way.
Example usage:
function myButtonPressed(){
// make an $http call
var $http = angular.$$injector.get('$http');
$http.get(/*......*/);
}
Update:
If you want to know how angular works I can try to explain the concept by keeping it simple.
Angular has some kind of "singleton repository" (that in some cases depends on the context of execution - like $scope) where all objects has to be registered in varius ways.
Now, when you define a controller (that is a function) in Angular using the module.controller( function or array here ) it stores your function and it assigns a $inject property (like you do in your example) with and array of "singleton's identifiers".
Angular knows when to execute your controller's function and knows how to call it so:
Angular looks in the $inject property and using $injector service obtains all the instances in the exact order and puts them into an array argsArray
Angular uses YourControllerFunction.apply(null, argsArray) to execute the controller's code
Il this the information you were looking for?

Related

Error Unknown provider: $scopeProvider <- $scope when using $injector to inject $scope into controller

When using DI in this fashion:
var MainController = function MainController($scope) {
//use $scope here
};
MainController.$inject = ["$scope"];
It, work but, when it's used like this:
var MainController = function MainController($injector) {
var $scope = $injector.get("$scope");
};
MainController.$inject = ["$injector"];
This will result in error:
Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope
Here's a plunker with an example that showcases the error, check the comments for an alternative to see that only the scope not custom services are affected by this.
I found this Angular bug where they talk about the controller being instantiated before the child $scope is being created as Tomer Avni answered, so:
why the first way of injecting $scope work while the second doesn't?
And is there a way to use the second method of Dependency injection
with $injector to inject $scope?
I've replied to you on gitter, but answering here as well as it may help other who have the same issue.
When you give Angular a function to call that has values that will be derived from dependency injection (e.g. a service, a controller, etc.), Angular will:
Look for an .$inject property on the function object which should be an array of dependency names as strings (e.g. ['$scope']).
If $inject is undefined, it will use the parameters of the function definition (which works in most cases, unless you minify your code and the names get mangled).
So, in a nutshell, it will look up the names you have specified in the DI container.
$scope doesn't exist in the DI container, only $rootScope does. So, if you're accessing the injector directly and asking for an instance of $scope, you'll get the injection error you've seen here.
In the example where it worked, you're not accessing the injector directly but instead relying on Angular working out how to create your controller. That's a subtle difference, but in this case an important one. Within Angular, when creating the controller instance it will resolve $scope to the result of calling $rootScope.$new() (i.e. what you would do when manually instantiating the controller in a unit test).
I hope that explains why your example didn't work.
As for the second question, you could manually get a scope instance by doing something like:
var $scope = $injector.get('$rootScope').$new();
But now we're starting to go down a pretty murky path... It's quite atypical to inject the $injector directly. I'd avoid that if you can. Is there a reason you need to do this?

Inject a factory in a controller in angular

I´m trying to inject a factory in a controller in Angular, but I can not do. It is my code.
app.controller('ManejadorEventosVista', ['AdministradorMarcador', ManejadorEventosVista]);
'app' is the variable that corresponds to the module with their respective dependencies. The controller is 'ManejadorEventosVista' and requires the services provided by the factory 'AdministradorMarcador'.
function ManejadorEventosVista(){}
but when I want to use the factory 'AdministradorMarcador' in this part of the code , the factory is not recognized.
ManejadorEventosVista.prototype.seleccionarMarcadorOrigen = function (){
AdministradorMarcador.setTipoMarcador(AdministradorMarcador.MARCADOR_ORIGEN);
};
How I can do to use the factory
'AdministradorMarcador' in ManejadorEventosVista.prototype.seleccionarMarcadorOrigen??..
Help or example to guide me??..Thanks..
ManejadorEventosVista needs to take an argument and you will be able to reference the AdministradorMarcador inside the function as whatever you named the first variable. Like so
function ManejadorEventosVista(AdministradorMarcador){/**your code here**/}
What you are doing with the line fragment ['AdministradorMarcador', ManejadorEventosVista] is declaring that your function depends on AdministradorMarcador, but without providing an argument to ManejadorEventosVista, AngularJS doesn't know how you intend to reference AdministradorMarcador inside your controller.
This is done in order to allow AngularJS scripts to be minified, especially by already existing solutions, as they would change the variables your function takes to single letter names, making it impossible for AngularJS to determine which service or factory to inject. Annotation uses strings and position-based ordering to allow your script to work, even after being minified since strings won't be altered by the process.
See also Latest Stable docs on Annotation

Prevent Third Party Factory Interfering with My App's Factory with the Same Name

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?
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!
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.
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 !!!
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

Using controllerAs syntax in Angular, how can I watch a variable?

With standard controller syntax in AngularJS, you can watch a variable like:
$scope.$watch(somethingToWatch, function() { alert('It changed!'); });
Using the controllerAs syntax, I want to react to this change in an active controller. What's the easiest way to do this?
More detail, if it helps. I have one controller in a side pane that controls the context of the application (user selection, start time, end time, etc.). So, if the user changes to a different context, the main view should react and update. I'm storing the context values in a factory and each controller is injecting that factory.
You can always use a watcher evaluator function, especially helpful to watch something on the controller instance or any object. You can actually return any variable for that matter.
var vm = this;
//Where vm is the cached controller instance.
$scope.$watch(function(){
return vm.propToWatch;
}, function() {
//Do something
}, true);//<-- turn on this if needed for deep watch
And there are also ways to use bound function to bind the this context.
$scope.$watch(angular.bind(this, function(){
return this.propToWatch;
//For a variable just return the variable here
}), listenerFn);
or even ES5 function.bind:
$scope.$watch((function(){
return this.propToWatch;
}).bind(this), listenerFn);
If you are in typescript world it gets more shorter.
$scope.$watch(()=> this.propToWatch, listenerFn);
Eventhough you can watch on the controller alias inside the controller ($scope.watch('ctrlAs.someProp'), it opens up couple of problems:
It predicts (or in other words pre-determines) the alias used for the controller in the view/route/directive/modal or anywhere the controller is used. It destroys the purpose of using controllerAs:'anyVMAlias' which is an important factor in readability too. It is easy to make typo and mistakes and maintenance headache too since using the controller you would need to know what name is defined inside the implementation.
When you unit test the controller (just the controller), you need to again test with the exact same alias defined inside the controller (Which can probably arguably an extra step if you are writing TDD), ideally should not need to when you test a controller.
Using a watcher providing watcher function against string always reduced some steps the angular $parse (which watch uses to create expression) internally takes to convert the string expression to watch function. It can be seen in the switch-case of the $parse implementation

How to dynamically inject controller in AngularJS

How to set ng-controller as an expression from the $scope?
According to the documentation:
ngController – {expression} – Name of a globally accessible
constructor function or an expression that on the current scope
evaluates to a constructor function.
But how to evaluate scope expression as a controller for controllers that have been registered with module .controller?
For example:
Layout:
<div ng-controller="myExpr"></div>
JavaScript (define controller):
app.controller('myCtrl', ['$scope', '$timeout', function () { ... }];
JavaScript (parent scope):
$scope.myExpr = ...;
What should be in myExpr to use myCtrl as a controller via expression?
I've tried $controller('myCtrl')... not working...
P.S. If controller has been defined via globally accessible function.. it's possible to provide it as myExpr. But what to do if it has been defined so?
The expressions that ng-controller accept are a bit wierd. So you can do this by writing your controller slightly differently (but read below as for why you probably don't want to).
function myCtrl($scope) {
$scope.value = 'Stuff';
}
This is a controller and will work like normal for this case. Like in this example: http://jsbin.com/ubevel/2/edit
So why not do it?
First of all this is not a good way to define things from a testing perspective. Secondly, this allows you to set the controller dynamically on load but it won't allow you to change things after that. If you change the value of myExpr after the page has loaded the controller will not change.
So what to do?
I would highly suggest looking at using a service instead. Swap out your actions by supplying your outer controller with a service that you then change in the same manner you are now trying to change the inner controller. So something like: http://jsbin.com/ubevel/5/edit
This service can be swapped out on the fly, changing it will change the actions that are available in the scope.
You could also use an include, but this would result in duplicate html. I personalty am fine with this since I am against reusing html for two different types objects (sooner or later you want to change one but not the other and it becomes a mess). But a lot of people would object to that.
An extra note: There are probably nicer ways to do it with controllers, I probably haven't looked at all angles, but I just don't think controllers are the right tool for this case.

Categories

Resources