I have a simple angularjs directive and when I minify the file i get error because the variable names are changed
var app= angular.module('app',[]);
app.directive('directive',function(){
return {
restrict: 'EA',
scope: {},
replace: true,
link: function($scope, element, attributes){
$scope.name="test-test";
,
controller: function($scope,$attrs,$http){
},
templateUrl: 'directives/app/app.tpl.html'
}
});
the problem is $scope.name changes into a.name and angular doesn't recognize it.
I tried injecting $scope by trying something like
link: ['$scope',function($scope, element, attributes){
$scope.name="test-test";
}],
controller: ['$scope','$attrs','$http',function($scope,$attrs,$http){
}],
but I still get the same error of a.name when minified.
Directives link functions aren't injected. They're passed fixed set of parameters which are listed and comprehensively described in angular.js documentation. However this is not the case with controller. These are injected and should be annotated before minification. You can do it in at least 3 ways:
using array syntax like in your example
setting $inject property on controller function with value being array of names of injectables
annotating with ngAnnotate which will detect uses of angular injection and annotate it properly
The link function is not dependency injected. It uses only positional arguments so you don't have to use that explicit array naming. The controller one is fine I think.
At last, remember, you must write code for clarity. The explicit syntax is a bit too verbose, that's why most people choose to use ng-annotate instead. Ng-annotate is a compilation step which will convert the code to the explicit form before minification.
Related
I'm new to AngularJS and I would like to understand more about the dependencies that are being injected by default. While reading through code I've noticed that sometimes dependencies are explicitly declared beforehand, and sometimes they aren't. For example:
someModule.controller('MyController', ['$scope', 'someService', function($scope, someService) {
// ...
}]);
Gives the same results as:
someModule.controller('MyController', function($scope, someService) {
// ...
});
How does this work? Is Angular assuming that the modules being injected are named the same as the variables in the parameters?
Also, strangely enough, if you do specify the dependencies that are going to be injected, you must specify all of them and in the right order, otherwise nothing will work. For example, this is broken code:
someModule.controller('MyController', ['someService', '$scope', function($scope, someService) {
// Won't give us any errors, but also won't load the dependencies properly
}]);
Can someone clarify to me how is this whole process working? Thank you very much!!
Yes, dependency injection in Angular works via the names of the components you (and Angular - for the internal ones) registered.
Below is an example showing how a service is registered and injected into a controller using several different annotations. Please note that dependency injection always works the same in Angular, i.e. it doesn't matter if you are injecting something into a controller, a directive or a service.
app.service('myService', function () {
// registering a component - in this case a service
// the name is 'myService' and is used to inject this
// service into other components
});
Two use (inject) this component in other components, there are three different annotations I am aware of:
1. Implicit Annotation
You can either specify a constructor function which takes as parameters all the dependencies. And yes, the names need to be the same as when these components were registered:
app.controller('MyController', function ($http, myService) {
// ..
});
2. Inline Array Annotation
Or you can use a notation using an array, where the last parameter is the constructor function with all the injectables (variable names do not matter in this case). The other values in the array need to be strings that match the names of the injectables. Angular can this way detect the order of the injectables and do so appropriately.
app.controller('MyController', ['$http', 'myService', function ($h, m) {
/* Now here you can use all properties of $http by name of $h & myService by m */
// Example
$h.x="Putting some value"; // $h will be $http for angular app
}]);
3. $inject Property Annotation
A third option is to specify the $inject-property on the constructor function:
function MyController($http, myService) {
// ..
}
MyController.$inject = ['$http', 'myService'];
app.controller('MyController', MyController);
The reason why the last two options are available, at least as far as I know, is due to issues which occured when minifying the JavaScript files which led to the names of the parameters being renamed. Angular then wasn't able to detect what to inject anymore. In the second two cases the injectables are defined as strings, which are not touched during minification.
I would recommend to use version 2 or 3, as version 1 won't work with minification/obfuscation. I prefer version 3 as from my point of view it is the most explicit.
You can find some more detailed information in the internet, e.g. on the Angular Developer Guide.
Just to provide a different sort of answer, as to the how inline/implicit dependencies work in AngularJS. Angular does a toString on the provided function and parses the parameter names from the string which is produced. Example:
function foo(bar) {}
foo.toString() === "function foo(bar) {}"
References:
source code
AngularJS Dependency Injection - Demystified
I learnt to write angular dependencies needed using the array notation, that way:
var app = angular.module('MyApp', []);
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.stuff = 'stuff';
}]);
The Angular doc follows this notation, but I see more and more tutorials not using the array notation and just directly passing the controller the function($scope).
Is there any differences between the two ways to do? Or maybe one was implemented in the version two?
You should be using the Array notation
say Tomorrow if you wish to minify your data using a uglify say, it minifies your big variable names but doesn't touch your strings so your statement
from
Case 1 with array notation
original
app.controller('MyCtrl', ['$scope', function($scope) {
minified
x.controller('MyCtrl', ['$scope', function(a) {
Here controller knows exactly knows that variable a is $scope
Case 2 without array notation (whereas if you choose not to use it)
original
app.controller('MyCtrl', function($scope) {
minified
x.controller('MyCtrl', function(a){
Now your controller doesn't know what to do with a variable its not $scope for sure
The array notation is important if you plan to minify your code, which you should be doing in production anyway. Stick to using it.
If you're planning to mini your app, yes you MUST use the array notation.
This is because the variables are renamed, so the injector no longer knows what dependencies you're intending to inject.
For example if $scope was renamed a on minification it wouldn't work.
Obviously this means you have to write and maintain more code. Luckily, you can automate this in your build process.
On my project I use grunt and angular-templates.
https://www.npmjs.com/package/grunt-angular-templates
Yes there is a difference. I would recommend continuing to use array notation since not using it will break your dependency references if the application is minified. For more details, read https://docs.angularjs.org/tutorial/step_05 .
Basically you can rename the variables without affecting angulars ability to inject. As others have said, specifically for minification.
var app = angular.module('MyApp', []);
app.controller('MyCtrl', ['$scope', function(s) {
s.stuff = 'stuff';
}]);
Array notation is used for injecting the dependencies. Difference between this two is if u do not include array notation means u do not need any dependencies.
Best practice is to use [] notation so that u can include dependencies any time in you application.
In a nutshell there are two ways how you can use dependency injection in angular: implicit or explicit.
Implicit DI is .controller('MyCtrl', function(scope, dep1, dep2){
It does not give instructions to $injector on what to include, it bvasically doing something like a reflection to figure it out. If you minify your code it will turn into
`.controller('MyCtrl', function(a, b, c){ `
That is why we are using explicit DI. There are several ways to do so:
use array : .controller('MyCtrl', ['$scope','dep1','dep2', function($scope, dep1, dep2){
use $inject:
.controller('MyCtrl', myCtrl)
myCtrl.$inject = ['$scope', 'dep1', 'dep2']
function myCtrl($scope, dep1, dep2) {}
In both ways, even if minification renamed function parameters to something, $injector will know what it is expected to inject, as it has original names in the string literals ( which are not affected by minification)
You can also use comment annotations that will tell compiler/transpiler how to handle angular DI , ie it will turn implicit DI into explicit for you, see ng-annotate
I personally prefer second way with .$inject
DI helps you while minifying the code, Also other answer does explained how both the DI injection techniques work. Basically none of DI injection technique is bad.
But I'll prefer to do not worry about this thing because other will take care of this DI thing. Use ng-annotate directive, that will give you facility to use dependency inside a function directly
If you wrote code like this
angular.module("MyMod").controller("MyCtrl", function($scope, $timeout) {
//awesome code here
});
When you send this code to minifier it does add the array annotation of dependency on the angular component while
angular.module("MyMod").controller("MyCtrl", ["$scope", "$timeout",
function($scope, $timeout) {
//code here
}
]);
From Docs
ng-annotate works by using static analysis to identify common code
patterns. There are patterns it does not and never will understand and
for those you can use an explicit ngInject annotation instead, see
section further down.
I am passing a custom scope object to the $compile and creating a custom template. If I apply a directive on the elements inside the template, scope that is changing is the one that is passed to the $compile, and that's really what I wanted.
However, I just thought that it might be good to also have a controller on some elements inside the template,
<div ng-controller="controllerName" >
</div>
but ng-controller doesn't set data on the passed scope but creates its own and uses that one. Is there a way to make ngController to use existing scope and not create a new one ?
We create our controllers and wrap them in factories to make them accessible. We apply or controllers through directives (also going away). This gives you a controller that is scoped to the directive, which has better control for scope, this works for us as the directives where we do this for are usually components.
I don't know if this will be an option given the road you are down now. I would suggest trying to stop using ng-controller. You may want to look at angular 2 now just to keep it in mind as a migration path, it is coming in the fairly near future. They have removed ng-controller, a lot of what they are doing in angular 2 can be done now.
This is a good resource on why these things are a bad idea
https://www.youtube.com/watch?v=gNmWybAyBHI&t=9m10s
If you look at the source code for ng-controller, you will see it is very simple:
var ngControllerDirective = [function() {
return {
restrict: 'A',
scope: true,
controller: '#',
priority: 500
};
}];
You can actually create an almost identical alternate directive that just defines scope: false (or omits the scope key altogether, same thing):
app.directive('controllerNoScope', function () {
return {
restrict: 'A',
scope: false,
controller: '#',
priority: 500 // same as ng-controller
}
});
(You may want to give it a better name).
See this Plunkr for a demo that shows the scope has the same $id as the outer one, meaning it is the same scope.
I have a some experience with AngularJS and have come across this directive on the web, but it is not like anything I have seen before and I am unable to comprehend what it is doing? Can anyone help?
Specific questions: With the little understanding, the signature of the directive must be doing dependency injection. But what I am struggling with is: if $injector is passed in the array, why is it also sent as a parameter in function i.e. function($injector); in other words why are there two $injectors? What will not work if I dont send the $injector in the array?
Also how is it that this directive has got controller embedded? When do you define such controllers?
Also I normally see scope with a $ prefix in the the code below how is it working without a $?
Any links to read more or explaining it here will be useful.
.directive('mycomp', [
'$injector', function($injector) {
var $builder, $compile, $drag;
$builder = $injector.get('$builder');
$drag = $injector.get('$drag');
$compile = $injector.get('$compile');
return {
restrict: 'A',
scope: {
component: '=mycomp'
},
controller: 'mycompController',
link: function(scope, element) {
scope.copyObjectToScope(scope.component);
$drag.draggable($(element), {
mode: 'mirror',
defer: false,
object: {
componentName: scope.component.name
}
});
return scope.$watch('component.template', function(template) {
var view;
if (!template) {
return;
}
view = $compile(template)(scope);
return $(element).html(view);
});
}
};
}
])
why is it also sent as a parameter in function i.e. function($injector); in other words why are there two $injectors?
When doing array-type injection it really doesn't matter how parameter in function is called, it will map to array items. For example, if we have
['$injector', function(a) {..}]
Parameter a will map to $injector instance, and if we have
['$injector', '$scope', function(a, b) {..}]
a will map to $injector instance and b will map to $scope instance. The order here is what matters. More here: https://docs.angularjs.org/tutorial/step_05 in A Note on Minification section.
What will not work if I dont send the $injector in the array?
If you don't, $injector will be undefined, some of that explained above.
Also how is it that this directive has got controller embedded? When do you define such controllers?
Some directives can have controllers if needed, they should hold some heavier logic, $scope binding and so on. link function actually should hold only interactions with $element.
More here: http://www.sitepoint.com/practical-guide-angularjs-directives/
Also I normally see scope with a $ prefix in the the code below how is it working without a $?
In this case scope is used in link function and it is NOT an injectible. In this case it is a simple variable scope which refers to controllers scope. You can call it superBigVariableName and it would still refer to scope and would still work.
So keep in mind, that link function is actually a simple function where first attribute is scope, second is element, third is attributes, you can't inject services into your link function (do that logic in controller)
I've got a tiny problem with an angular directive that's now working and I don't know why. I think it's a fairly simple issue that I'm overlooking, maybe you can help me out.
Directive is defined like this:
angular.module('directives', [])
.directive('my-directive', function () {
return {
restrict: 'AE',
scope: {
name: '=name'
},
template: '<h1>{{name}}</h1>'
};
});
Then index.cshtml:
<my-directive name="test"></my-directive>
Application.js:
var app = angular.module('MyApp', [
...,
'directives'
]);
And here's controllers.js
angular.module('controllers', ['apiServices', 'directives'])
.controller('homecontroller', function($scope, $resource, webApiService, $log, $translate, $localStorage, $sessionStorage) {
Ok confirmed that directives.js is loaded, otherwise application.js nags about 'unknown module'. There are no error messages in the console, the thing just doesn't show. Any ideas?
EDIT
So as pointed out, I changed the directive name to camelCase, but still no luck:
<my-directive name="John Doe"></my-directive>
And
.directive('myDirective', function () {
But nothing is showing yet.
EDIT
Problem is that angular expects an object to be passed into the attribute, not a string literal. If you create an object person = { name: 'John' }, pass the person in, then write {{ person.name }} ( assuming we named the attribute person + scope var person too ).
During normalization, Angular converts - delimited name to camelCase.
So use camelCase while specifying the directive inside JS:
.directive('myDirective', function () {
Fiddle
I'm sure you've figured this out already, but if you change your scope definition for name to be
scope: {
name: '#'
}
you will then be able to pass a string. The '#' interpolates the attribute while the '=' binds it. Additionally, you don't need to include an attribute name if it is the same as the scope variable.
The problem appears to be in the directive definition. You note in your question that Angular expects an object; this is true for the "=" scope, but not for the "#" scope. In the "#" scope, Angular expects a string only. I have created a snippet below.
Too many modules
Unless you are reusing the directive in multiple applications, do not create a new module for it. Add the directive definition to the module that you created for the application. In my example below, I called the module back by using "angular.module( moduleName )"... When only one argument is used, Angular returns the existing object rather than creating a new one. This is how we can separate the code into many files.
Things to Note
You will notice the following:
You do not need to load the module into the app variable. Calling the Singleton each time is actually safer and easier on memory management.
The directive is in camel case, as you have already noted.
I am setting the name attribute to a string value and not an object; this works because of the "#" scope setting.
The div is set to ng-app='MyApp'. This usually is set to the html element, but I did not want to mess with the DOM on Stack Exchange. The ng-app directive can be set on any element, and the directives associated with that module will be applied on all elements that are within that element's scope. Without the ng-app directive, Angular does not know which module to run on the page.
//app.js - this defines the module, it uses two parameters to tell the injector what to do.
angular.module('MyApp',[]);
//directive.js stored elsewhere
//this calls back the module that has been created. It uses one parameter because the injector is no longer needed.
angular.module('MyApp').directive('myDirective', function () {
return {
restrict: 'AE',
scope: {
name: '#'
},
template: '<h1>{{name}}</h1>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="test"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
</div>
Using Injection
When you have a different module for each and every directive or controller, each one must be injected into the application's module definition. This leaves a lot of room for error. As a best practice, only create a new module when necessary, and make the module a container for a group of related functionality and not a single item.
The code below demonstrates proper injection.
angular.module( "MyApp", ['ReusableDirectives'] );
angular.module( "MyApp" ).directive( "myDirective", function(){
return {
restrict: "AE",
scope: { name: "#" },
template: "<p>This is the directive I defined in the example above. It uses <u>the same module</u> as my main application, because it is not going to be reused over and over again. In fact, I will need it just for this application, so I don't need to complicate things with a new module. This directive takes an attribute called 'name' and if it is a string allows me to manipulate the string within my templates scope to do things like this: {{'hello ' + name + '!'}}</p>"
};
} );
angular.module( "ReusableDirectives", [] );
angular.module( "ReusableDirectives" ).directive("reusableDirective", function(){
return {
restrict: "E",
template: "<p>This is a directive that I intend to use in many, many applications. Because I will reuse it so much, I am putting it in a separate module from my main application, and I will inject this directive. This is the only reason that this directive is not in the same module as the one I defined above.</p>"
};
} ).directive("reusableDirective2", function(){
return {
restrict: "E",
template: "<p>This is a second directive that I intend to use in multiple applications. I have stored it in a module with the first directive so that I can freely inject it into as many apps as I like.</p>"
};
} )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="Johnny"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
<h3>Injected Directives</h3>
<reusable-directive></reusable-directive>
<reusable-directive2></reusable-directive2>
</div>
Keep it simple. Define your directives on a single module for your application. Once you have that done and working, if you need the directives again in another application, refactor and experiment with injections at that time after you have some more Angular practice under your belt.
You have a bright future with Angular, keep up the good work!
Your directive must be camel-cased
.directive('myDirective', function () {
then in your html, your are free whether to call it my-directive or myDirective
Both are valid
<my-directive name="test"></my-directive>
<myDirective name="test"></myDirective>
Just to follow up on this, I had to use the following way to get my directive to work.
<my-directive name="test"></my-directive>