-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathAngular Directive Notes
More file actions
244 lines (194 loc) · 17.7 KB
/
Angular Directive Notes
File metadata and controls
244 lines (194 loc) · 17.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
Angular Directives
Directives are JavaScript functions that maniuplate or extend the DOM that are placed in the DOM as attribute or custom elements (or classes or comments).
A directive is declared like so:
angular.module('example')
.directive('dirName', () => {
return {
// directive object goes here
};
});
So a directive is a JavaScript factory function (a function that returns an object) that returns an object specifying all the properties of the directive. A directive can include the following 11 properties:
restrict
require
transclude
scope
bindToController
controller
controllerAs
compile
link
template
templateUrl
In general you want to use a directive as an attribute or a custom element. The name of your directive specified as .directive('nameGoesHere') is what goes in the DOM, but the name in the DOM is kebob-cased and the kebab-case gets translated to camel-casing in the JavaScript directive definition. So a directive named 'myDir' would be used in the DOM the following way:
element: <my-dir></my-dir>
attribute: <div my-dir></div>
However, you want to namespace all of your custom directives so that it is unlikely they will ever be confused with other third party directives in case you use other angular modules in your app or make your own public angular module for other people to use. For example all of Angular's built in directives are namespaced with the 'ng' prefix, so if we were to namespace a directive with 'tk' an example would be 'tkMyDir' which would be used in the DOM as:
element: <tk-my-dir></tk-my-dir>
attribute: <div tk-my-dir></div>
Directives are basically of the web component paradigm, in that you are creating a re-usable custom element in the DOM. You can use a directive anytime you want to create a piece of UI, and you define the HTML template and any functionality for that UI piece in the directive. Directives are especially useful when you have a some HTML code and funtionality that you need to use multiple times in an application, that way you define the directive once and use it anywhere you need to, without having to rewrite code. You can also create your application entirely out of directives and completely avoid using views and controllers (though you still use controller inside directives) in your angular application, which is what Angular 2 will do anyways as it is changing the directive name to components because Angular 2 is going the full web component route to front end development.
The properties of directives:
restrict: 'AECM'
'A' - attribute
'E' - element
'C' - class
'M' - Comment
Directives in classes or comments are really only for older versions of IE,
so stick to just attributes or elements. Should use directive as an attribute
when you want to add functionality to some element or add some html to it.
Should use directive as a custom element when you want to insert html into
the view.
template:
You set a string with plain text or html straight onto this property. This
will be inserted where the directive is called. Use this for only the simplest
of templates, like a single line of html that you need to insert.
templateUrl:
This takes an html file and inserts it into the page where the directive is
called. This is what you use instead of the template property when you have
anything but the simplest of templates to insert. The template on this property
must have a single root element, meaning that you need to wrap everything in
this template partial under a single element, like a div. This template gets
placed in the page as a child of the directive element. There was a replace
property on directives that would replace the directive element with the
template specified in templateUrl so that the directive would not actually
be in the page, but the replace property has been deprecated so don't use it.
scope:
The scope property can take three different values: true, false, or an object.
The three types of directive scopes are shared, inherited, and isolated.I herited and isolated scopes create new scope objects. And by shared I just mean it uses the same scope as the parent controller. They are described below.
By default a directive's scope is set to false, which means it has a shared scope (it is does not have an inherited scope). This means that it shares the exact same scope with the controller attached to its parent html file. In this case you don't need to set the scope property at all.
If you set scope to true this means the directive has an inherited scope of its
parent controller. This means it inherits anything in the controller scope but
anything you create on the directive's controller cannot be accessed by the
parent controller.
If you set the scope property equal to an object this means the directive gets an isolated scope. If you give it an empty object, scope: {}, it is not connected in any way to the parent controller scope. If you do need some specific data that lives on the parent controller scope then you can pass those into the isolated directive scope by listing them as properties of the scope property and giving them a value of '='. In the html where you call the directive you would then put that directive scope property as an attribute on the html directive element and set it equal to the controller scope property you want to pass in. You can also use '=someName' and then use a different name in the directive call than what you are calling the scope variable in the directive, see the example below. Specifically '=someName' tells the $compile function to bind that property of the directive scope to 'someName', or in the example below to bind address to addy, which is equated to address1 from the controller scope.
i.e. You have a $scope.user1 and $scope.address1 you want to pass into the directive:
html:
<your-directive user='user1' addy='address1'></your-directive>
directive:
return {
restrict: 'E',
scope: {
user: 'user1',
address: '=addy'
},
.... use user or address in template or controller on directive...
}
There are three ways to pass in something to an isolated scope in a directive. As shown above you can pass in data from the controller scope with the '='. You can also pass in simple value parameters, by which I mean you can directly pass in values, using the '@'. You put the key in the scope as an attribute on the html element that calls the directive and set it equal to the value you want to pass in. In the directive on the scope property you then set the value of that key as '@'. You are limited to only passing in strings, so in the example below 'true' isn't a boolean, it is a string.
Also note that if you give a property on the isolated directive scope a camelCase key then in the html it needs to be kabob-cased because that is how html attributes are written. And just like with the '=name' you can do '@name' to specify a different name for the html attribute than the key you are specifying in the directive, as shown below.
You might do this because you can only take string value, but you want to use it as a boolean value and use the same name as the attribute in the directive, shown below with the $scope.something variable.
i.e.
html:
<your-directive string-value='hello' something='true'></your-directive>
directive:
return {
scope: {
stringValue: '@',
blah: '@something'
},
controller: function($scope) {
$scope.something = ($scope.blah === 'true')
}
}
The third way to pass in parameters to a directive is passing in a function. To do this you use the '&' as the value of the parameter on the isolated scope in the directive. You can also specify the name you want to use to grab that function from the html just the same as the other two ways, like '&someName'.
The '&' not only binds the method to your property on the scope of the directive, but any arguments you give to that method are also automatically bound as well, and you don't even put parameters on the function inside the directive. You can actually override the parameters that are passed into the function that is passed into the directive. When you call the function (without any parameters since it automatically binds them) you can override the function parameters by specifying an object as the parameter in which the keys are any parameters you want to override and the value is the value you want to override whatever value was passed in.
You can't use isolated or inherited scope more than once on a single element. So if you have two different directives as attributes on an html element, or a directive as an element and an attribute directive on that, only one can have either isolated or inherited scope. For this reason it is best practice to use shared scope for directives that are attributes in order to avoid this potential error.
A reason you would use an isolated scope is if you need to call a directive multiple times in a single html file. If you put two inherited scope directive attributes on the same element they will inherit the same scope, which could cause problems of overwriting, so don't do this.
controller:
You can define a controller in a directive with the controller property. It takes
an anonymous function with the services to be injected. You would want to use
this when there are properties you want to put on scope or other funcationlity
that needs to be done in the controller that should be contained entirely within
the directive rather than coming from the parent controller. Think about it like
passing parameters into functions rather than using global variables in a function.
controllerAs:
A directive property that allows you to use controller-as syntax for controllers in directives. It takes a string that is the name of the context of the viewModel, the controller's scope context. For example:
controller: () => { //code... },
controllerAs: 'vmChat'
You can then assign properties directly to the 'this' keyword in the controller definition and use those properties in the template with vmChat.someProp and that's it!
link:
The link property on directives is where you define a function to run on the directive.
What's the difference between link and controller? The basic difference is that
controller can expose an API, and link functions can interact with controllers
using require. Best Practice: use controller when you want to expose an API to
other directives. Otherwise use link.
bindToController:
The bindToController property of directives is to be used when you are using controller-as syntax in directives. The bindToController property was introduced in Angular 1.3.
What bindToController allows you to do is not have to inject $scope into your directive controller (when using controllerAs syntax) to get the properties bound to the scope property that are coming in from outside the directive. So you can access properties bound to the isolated scope that are passed into the directive by using the 'this' keyword in the directive's controller instead of injecting and using $scope.
The bindToController property takes a boolean value. When true, in a directive with isolated scope that uses controllerAs syntax, the component's properties are bound to the controller rather than to the scope. That means that when the controller is instantiated the initial values of the isolated scope bindings are available on 'this' and future changes are also automatically available.
Example:
scope: {
someObj: '=',
someVar: '@',
someOtherVar: '&'
},
bindToController: true,
controller: () => { //code... },
controllerAs: 'vm'
Starting in Angular 1.4 you can use bindToController not only just for isolated scopes and you give it not just a boolean value, but you can use it in directives when there is any new scope created and you can specifically specify the properties to be bound to the controller by specifying an object literal as the value for bindToController instead of putting those properties on the scope.
That means the bindToController can be used in Angular 1.4 for both isolated and shared scopes. And instead of setting bindToController to true you can put the values straight on it, like so:
scope: {}, // could be set to true for shared scope
bindToController: {
someObj: '=',
someVar: '@',
someOtherVar: '&'
},
controller: () => { //code...},
controllerAs: 'vm'
compile
The compile function allows the directive to manipulate the DOM before it is compiled and linked, thereby allowing it to add, remove, change directives or other DOM elements. Basically the compile function attaches event-listeners to DOM elements. It is actually defined as an object, not a function. It can run its operations and can also return the pre-link and post-link functions.
compile: {
// operations
pre: () => {},
post: () => {}
}
require
Require is used when you want your directive to be able to communicate with a parent directive.
When you want to use use a parent scope in your directive's link function in order to communicate with it you use the require property. To require a parent directive's scope in your link function you give the require property a string that is the name of a parent directive, prefixed by a carrot symbol, which tells the directive to search up the scope tree for that directive name. For example if your directive has a parent directive called 'ppDir' you would set your directives require property as such:
require: '^ppDir'
and then in your link function the fourth parameter is the scope of that parent directive:
link: (scope, elem, attrs, parentScope) => {}
translude
Transclude takes a boolean value, so when you use transclude you will set it to true. You would use transclude when you want to put some HTML as the inner content of your directive and have that HTML passed to the directive.
For example:
<my-dir>
<h1> This is transcluded! </h1>
<p> It is passed to the directive when transclude is set to true </p>
</my-dir>
The h1 and p DOM element are then passed to the directive and can be included in the directive's template using the ng-transclude directive. So if the directive's template was:
<div>
<section ng-transclude></section>
</div>
Then the resulting HTML for the directive when run in the browser would be:
<my-dir>
<div>
<section>
<h1> This is transcluded! </h1>
<p> It is passed to the directive when transclude is set to true </p>
</section>
</div>
</my-dir>
In summary, transclude is when you want to bring content from the HTML template into your directive's template by putting that content between the opening and closing tags of your directive.
The functions of directives:
There are four functions associated with a directive:
compile
controller
pre-link
post-link
The link directive property is the post-link function, so the compile, controller, and link directive properties refer to the compile, controller, and post-link functions. The pre-link function is defined inside the compile function, additionally the post-link function can be defined inside the compile function, they are defined in the compile function as pre and post.
i.e.
compile: {
// compile code here...
return {
pre: () => {},
post: () => {}
};
}
Again, defining the post-link function in the compile function (as 'post') is the same as defining the directive property 'link'. In most directives only the link or controller functions are needed.
The compile function allows the directive to manipulate the DOM before it is compiled and linked, thereby allowing it to add, remove, change directives or other DOM elements. Basically the compile function attaches event-listeners to DOM elements.
The controller function is for directive-to-directive communication. Child and sibling directives can request the controller of their parent and sibling directives to communicate data. It is best to only use the controller for methods and properties that are to be passed to child directives, anything else should go in the link function. Also transcluded content is not available when the directive controller runs, because the controller's purpose is not to alter the directive's content, only to pass data or functions to children directives.
The pre-link function allows for private $scope manipulation before the post-link process begins. The pre-link function is where you would put code that you do not want to be used by child and sibling directives because it executed private code involving the $scope. It can be used to execute code that modifies the $scope of its directive. ng-init uses pre-link.
The post-link function is the primary method to handle operations of the directive.
The controller and link functions can be used interchangeably, but you only need to choose the controller function over the link function when you need to communicate with other directives. If no communication is required, just use the link function.
The order of these functions execution is as follows:
Execution order:
compile, controller, pre-link, post-link
All compile functions will execute (for parent and child directives) before anything else (from parent to child), then parent controller and pre-link functions will execute before child controller and pre-link functions. Finally after all that post-link functions will execute (from child to parent). Note that post-link functions are executed from in the children first before the parents.