I've got the following code sample and apparently I once again lack some background information.
When I use the greet directive, there is a binding happening on the controller level. For some reason it binds the element and the attribute values of the element to those corresponding variables ($attr, $element).
Is there a list with all the existing bindings which exist for the controllers?
I've done some research, but couldn't come up with anything in this direction.
directives.js
angular.module('TodoApp.directives', []).
directive('greet', function () {
return {
template: '<h2>Greetings from {{from}} to {{to}}<h2>',
controller: function($scope, $element, $attrs) {
$scope.from = $attrs.from;
$scope.to = $attrs.greet;
}
};
});
list.html
....
<div greet="Test1" from="Test2"></div>
...
Directive controllers have some special bindings. You can find them in the docs of the $compile service (here), search for "controller". Repeating here for completeness:
The controller is injectable (and supports bracket notation) with the following locals:
$scope - Current scope associated with the element
$element - Current element
$attrs - Current attributes object for the element
$transclude - A transclude linking function pre-bound to the correct transclusion scope. The scope can be overridden by an optional first argument. function([scope], cloneLinkingFn).
This is called Dependency Injection. Rather than having a fixed parameter list, Angular uses a DI container to inject the parameter when you need it. For example, if you need the $http service, just add it as a parameter - the order of the parameters does not matter.
The type of parameters you can inject are constants, values, factories, services, and providers.
Some are available to you 'out-of-the-box' from the Angular library:
Look here for Angular Providers and Services
Others, you get from third-party modules, or modules that you develop yourself.
Related
I have a parent zone builder controller which has multiple US States that can have several zip codes within each state.
Here is a breakdown of the basics of the format:
BUILDER CONTROLLER
app.controller("builderController", function($scope, $http, $rootScope, $timeout) {
$scope.zones = [];
});
ZIP CONTROLLER
app.controller("zipController", function($scope, $http, $rootScope, $timeout) {
$scope.zipCodes = [];
$scope.addZipCode = function() {
$scope.zipCodes.push({code: '', distance: '25mi'});
}
$scope.removeZipCode = function(index) {
console.log(index, 'index removing');
$scope.zipCodes.splice(index, 1);
}
});
There can be multiple zipControllers in one builderController inside of the builder controller I want to come up with an object or way to iterate through all of the Zip Controllers so that I can calculate a total distance and number of zip codes used, each time one of the child controllers is updated.
What is the most efficient way to do something like this in Angular?
BASIC GOAL
So there could be 4-5 zipController elements in one builderController I want to have a variable in the builderController called something like totalZipCodes which counts the total number of zip codes in each zipCodes array for each controller, How would I do that with a service or factory? Or is there another way?
Store the data in a factory and have the controller call the factory to do the calculations.
a general approach
As #PrinayPanday suggested in his comment, you should use something like a component/directive to do this. I'm prefacing this by saying I will be using the term directive and component interchangeably as I'm not sure of your constraints. I would achieve this by doing the following:
create a parent directive that defines a directive controller
create child directives that require the parent directive controller
when the child directive initializes, it will register itself with the parent controller
use the parent controller's list of child directive controllers to do what you need to.
You could also use the child's scope to declare a callback on some event (such as $onInit) rather than defining the directive controllers. I have answered several questions like this. So you might find these answers to be helpful in understanding the above solution more:
What is the relationship between an Angular 1.X controller and directive
Angular: Looking for an explanation on custom directive syntax
specific to your needs
As I'm rereading your question, I have to ask if there may not be a better way to solve this by sharing the data in a service/model rather than needing custom directives?
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'm a bit confused with the use of $scope in controllers and of scope in directives. Please verify if my understanding is correct (and also provide some alternative ways how to do this).
Let's say I have an html:
<div ng-controller="app1_Ctrl">
.
.
.
<input type="text" ng-model="value"/>
<input type="checkbox" />
<button ng-click="submit()"></button>
</div>
And my main.js
(function() {
angular.module('mainApp', ['app1']);
})();
And my app1 looks like this (based on official AngularJS documentation here)
(function() {
var app = angular.module('app1', []);
app.controller('app1_Ctrl', ["$scope", function($scope) {
.
.
.
}]);
app.directive('app1_Dir1', [function() {
function link(scope, element, attr) {
scope.$watch(attr.someAttrOfCheckBox, function() {
// some logic here
});
function submit() {
// some logic here
}
}
return link;
}]);
})();
How does $scope.value passed in scope in directive so that I can do some manipulations there? Will ng-click fire the function submit() in the directive link? Is it correct to use scope.$watch to listen for an action (ticked or unticked of course) in checkbox element?
Many thanks to those who can explain.
By default, directive scope is controller $scope; but it means the directive is directly dependent on your controller and you need a different controller for each instance of the directive you want to use. It is usually considered a best practice to isolate your directive scope and specifically define the variables you wish to pass it from your controller.
For this, you will need to add a scope statement to your directive :
scope {
label :'#',
context : '=',
function : '&'
}
and update your view :
<my-directive label="labelFromController" context="ctxtFromController" function="myFunction()" ></my-directive>
The symbols denote the kind of thing you wish to pass through : # is for one-way binding (as a string in your directive), = is for two-way binding of an object (which enables the directive to update something in your controller), and & is for passing a function.
There are a lot of additional options and subtleties that are best explained by the Angular doc https://docs.angularjs.org/guide/directive. There are also some nice tutorials out there (e.g. http://www.sitepoint.com/practical-guide-angularjs-directives/)
Your submit() function is not attached to anything, so you won't be able to call if from your viewer. You need to define it as scope.submit = function() ... in your link function if you wish to access it.
You can use $watch for this kind of thing, but there are usually other more elegant ways to achieve this by leveraging the fact that angular already "watches" the variables it is aware of and monitors any changes he can (this can be an issue when some external service changes data for exemple, because angular cannot listen to events it is not made aware of). Here, you can probably simply associate the ng-model directive to your input checkbox to store its true/fale (checked/unchecked) value, and the ng-change or ng-click directives to act on it. The optimal solution will mostly depend on the exact nature of your business logic.
Some additional thoughts :
The HTML insides of your directive should be packaged in an inline template field, or in a separate HTML file referenced by the templateUrl field in your directive.
In your HTML code above, your directive is not referenced anywhere. It should be an element, attribute or class (and your directive definition should reflect the way it can be called, with the restrict field). Maybe you have omitted the line containing the directive HTML, but as it stands, your directive doesn't do anything.
To my knowledge, you don't need to return link. Think of it as the "body" of your directive, where you define the variables and functions you will call in the HTML.
Your directive doesn't actually need HTML code and the above thoughts might be irrelevant if you are going in a different direction, but encapsulating some kind of view behaviour that you want to reuse is probably the most common use of directives.
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>
I need to perform some operations on scope and the template. It seems that I can do that in either the link function or the controller function (since both have access to the scope).
When is it the case when I have to use link function and not the controller?
angular.module('myApp').directive('abc', function($timeout) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: true,
link: function(scope, elem, attr) { /* link function */ },
controller: function($scope, $element) { /* controller function */ }
};
}
Also, I understand that link is the non-angular world. So, I can use $watch, $digest and $apply.
What is the significance of the link function, when we already had controller?
After my initial struggle with the link and controller functions and reading quite a lot about them, I think now I have the answer.
First lets understand,
How do angular directives work in a nutshell:
We begin with a template (as a string or loaded to a string)
var templateString = '<div my-directive>{{5 + 10}}</div>';
Now, this templateString is wrapped as an angular element
var el = angular.element(templateString);
With el, now we compile it with $compile to get back the link function.
var l = $compile(el)
Here is what happens,
$compile walks through the whole template and collects all the directives that it recognizes.
All the directives that are discovered are compiled recursively and their link functions are collected.
Then, all the link functions are wrapped in a new link function and returned as l.
Finally, we provide scope function to this l (link) function which further executes the wrapped link functions with this scope and their corresponding elements.
l(scope)
This adds the template as a new node to the DOM and invokes controller which adds its watches to the scope which is shared with the template in DOM.
Comparing compile vs link vs controller :
Every directive is compiled only once and link function is retained for re-use. Therefore, if there's something applicable to all instances of a directive should be performed inside directive's compile function.
Now, after compilation we have link function which is executed while attaching the template to the DOM. So, therefore we perform everything that is specific to every instance of the directive. For eg: attaching events, mutating the template based on scope, etc.
Finally, the controller is meant to be available to be live and reactive while the directive works on the DOM (after getting attached). Therefore:
(1) After setting up the view[V] (i.e. template) with link. $scope is our [M] and $controller is our [C] in M V C
(2) Take advantage the 2-way binding with $scope by setting up watches.
(3) $scope watches are expected to be added in the controller since this is what is watching the template during run-time.
(4) Finally, controller is also used to be able to communicate among related directives. (Like myTabs example in https://docs.angularjs.org/guide/directive)
(5) It's true that we could've done all this in the link function as well but its about separation of concerns.
Therefore, finally we have the following which fits all the pieces perfectly :
Why controllers are needed
The difference between link and controller comes into play when you want to nest directives in your DOM and expose API functions from the parent directive to the nested ones.
From the docs:
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.
Say you want to have two directives my-form and my-text-input and you want my-text-input directive to appear only inside my-form and nowhere else.
In that case, you will say while defining the directive my-text-input that it requires a controller from the parent DOM element using the require argument, like this: require: '^myForm'. Now the controller from the parent element will be injected into the link function as the fourth argument, following $scope, element, attributes. You can call functions on that controller and communicate with the parent directive.
Moreover, if such a controller is not found, an error will be raised.
Why use link at all
There is no real need to use the link function if one is defining the controller since the $scope is available on the controller. Moreover, while defining both link and controller, one does need to be careful about the order of invocation of the two (controller is executed before).
However, in keeping with the Angular way, most DOM manipulation and 2-way binding using $watchers is usually done in the link function while the API for children and $scope manipulation is done in the controller. This is not a hard and fast rule, but doing so will make the code more modular and help in separation of concerns (controller will maintain the directive state and link function will maintain the DOM + outside bindings).
The controller function/object represents an abstraction model-view-controller (MVC). While there is nothing new to write about MVC, it is still the most significant advanatage of angular: split the concerns into smaller pieces. And that's it, nothing more, so if you need to react on Model changes coming from View the Controller is the right person to do that job.
The story about link function is different, it is coming from different perspective then MVC. And is really essential, once we want to cross the boundaries of a controller/model/view (template).
Let' start with the parameters which are passed into the link function:
function link(scope, element, attrs) {
scope is an Angular scope object.
element is the jqLite-wrapped element that this directive matches.
attrs is an object with the normalized attribute names and their corresponding values.
To put the link into the context, we should mention that all directives are going through this initialization process steps: Compile, Link. An Extract from Brad Green and Shyam Seshadri book Angular JS:
Compile phase (a sister of link, let's mention it here to get a clear picture):
In this phase, Angular walks the DOM to identify all the registered
directives in the template. For each directive, it then transforms the
DOM based on the directive’s rules (template, replace, transclude, and
so on), and calls the compile function if it exists. The result is a
compiled template function,
Link phase:
To make the view dynamic, Angular then runs a link function for each
directive. The link functions typically creates listeners on the DOM
or the model. These listeners keep the view and the model in sync at
all times.
A nice example how to use the link could be found here: Creating Custom Directives. See the example: Creating a Directive that Manipulates the DOM, which inserts a "date-time" into page, refreshed every second.
Just a very short snippet from that rich source above, showing the real manipulation with DOM. There is hooked function to $timeout service, and also it is cleared in its destructor call to avoid memory leaks
.directive('myCurrentTime', function($timeout, dateFilter) {
function link(scope, element, attrs) {
...
// the not MVC job must be done
function updateTime() {
element.text(dateFilter(new Date(), format)); // here we are manipulating the DOM
}
function scheduleUpdate() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
scheduleUpdate(); // schedule the next update
}, 1000);
}
element.on('$destroy', function() {
$timeout.cancel(timeoutId);
});
...