Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit e69c287

Browse files
committed
docs(guide/directive,guide/compiler,): drastically improve
1 parent 32ab648 commit e69c287

File tree

6 files changed

+1391
-672
lines changed

6 files changed

+1391
-672
lines changed

docs/content/error/compile/ctreq.ngdoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
@fullName Missing Required Controller
44
@description
55

6-
This error occurs when {@link api/ng.$compile template compiler} tries process the directive that specifies the `require` option in a {@link guide/directive#writing-directives_directive-definition-object directive definition},
6+
This error occurs when {@link api/ng.$compile HTML compiler} tries to process a directive that specifies the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object `require` option} in a {@link api/ng.$compile#description_comprehensive-directive-api directive definition},
77
but the required directive controller is not present on the current DOM element (or its ancestor element, if `^` was specified).
88

99
To resolve this error ensure that there is no typo in the required controller name and that the required directive controller is present on the current element.

docs/content/error/compile/iscp.ngdoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ myModule.directive('directiveName', function factory() {
2121
});
2222
```
2323

24-
Please refer to the {@link guide/directive#writing-directives_directive-definition-object directive definition docs} to learn more about the api.
24+
Please refer to the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object
25+
`scope` option} of the directive definition documentation to learn more about the API.

docs/content/error/compile/nonassign.ngdoc

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
@fullName Non-Assignable Expression
44
@description
55

6-
This error occurs when a directive defines an isolate scope property that support two-way data-binding (using the `=` mode in the {@link guide/directive#writing-directives_directive-definition-object directive definition}) but the directive is used with an expression that is not-assignable.
6+
This error occurs when a directive defines an isolate scope property
7+
(using the `=` mode in the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object
8+
`scope` option} of a directive definition) but the directive is used with an expression that is not-assignable.
79

810
In order for the two-way data-binding to work, it must be possible to write new values back into the path defined with the expression.
911

docs/content/guide/compiler.ngdoc

+250-16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
@name Developer Guide: HTML Compiler
33
@description
44

5+
<div class="alert alert-warning">
6+
**Note:** this guide is targeted towards developers who are already familiar with AngularJS basics.
7+
8+
If you're just getting started, we recommend the {@link tutorial/ tutorial} first.
9+
If you just want to create custom directives, we recommend the {@link guide/directive directives guide}.
10+
If you want a deeper look into Angular's compilation process, you're in the right place.
11+
</div>
12+
13+
514
# Overview
615

716
Angular's {@link api/ng.$compile HTML compiler} allows the developer to teach the
@@ -27,9 +36,9 @@ All of this compilation takes place in the web browser; no server side or pre-co
2736
involved.
2837

2938

30-
# Compiler
39+
## Compiler
3140

32-
Compiler is an angular service which traverses the DOM looking for attributes. The compilation
41+
Compiler is an Angular service which traverses the DOM looking for attributes. The compilation
3342
process happens in two phases.
3443

3544
1. **Compile:** traverse the DOM and collect all of the directives. The result is a linking
@@ -44,7 +53,7 @@ for each item in a collection. Having a compile and link phase improves performa
4453
cloned template only needs to be compiled once, and then linked once for each clone instance.
4554

4655

47-
# Directive
56+
## Directive
4857

4958
A directive is a behavior which should be triggered when specific HTML constructs are encountered
5059
during the compilation process. The directives can be placed in element names, attributes, class
@@ -108,35 +117,260 @@ Here is a directive which makes any element draggable. Notice the `draggable` at
108117
</example>
109118

110119

111-
The presence of the `draggable` attribute on any element gives the element new behavior. The beauty of
112-
this approach is that we have taught the browser a new trick. We have extended the vocabulary of
113-
what the browser understands in a way which is natural to anyone who is familiar with HTML
114-
principles.
120+
The presence of the `draggable` attribute on any element gives the element new behavior.
121+
We extended the vocabulary of the browser in a way which is natural to anyone who is familiar with the principles of HTML.
115122

116123

117-
# Understanding View
124+
## Understanding View
118125

119-
There are many templating systems out there. Most of them consume a static string template and
126+
Most other templating systems consume a static string template and
120127
combine it with data, resulting in a new string. The resulting text is then `innerHTML`ed into
121128
an element.
122129

123130
<img src="img/One_Way_Data_Binding.png">
124131

125132
This means that any changes to the data need to be re-merged with the template and then
126-
`innerHTML`ed into the DOM. Some of the issues with this approach are: reading user input and merging it with data,
127-
clobbering user input by overwriting it, managing the whole update process, and lack of behavior
128-
expressiveness.
133+
`innerHTML`ed into the DOM. Some of the issues with this approach are:
129134

130-
Angular is different. The Angular compiler consumes the DOM with directives, not string templates.
135+
1. reading user input and merging it with data
136+
2. clobbering user input by overwriting it
137+
3. managing the whole update process
138+
4. lack of behavior expressiveness
139+
140+
Angular is different. The Angular compiler consumes the DOM, not string templates.
131141
The result is a linking function, which when combined with a scope model results in a live view. The
132-
view and scope model bindings are transparent. No action from the developer is needed to update
133-
the view. And because no `innerHTML` is used there are no issues of clobbering user input.
142+
view and scope model bindings are transparent. The developer does not need to make any special calls to update
143+
the view. And because `innerHTML` is not used, you won't accidentally clobber user input.
134144
Furthermore, Angular directives can contain not just text bindings, but behavioral constructs as
135145
well.
136146

137147
<img src="img/Two_Way_Data_Binding.png">
138148

139-
The Angular approach produces a stable DOM. This means that the DOM element instance bound to a model
149+
The Angular approach produces a stable DOM. The DOM element instance bound to a model
140150
item instance does not change for the lifetime of the binding. This means that the code can get
141151
hold of the elements and register event handlers and know that the reference will not be destroyed
142152
by template data merge.
153+
154+
155+
156+
## How directives are compiled
157+
158+
It's important to note that Angular operates on DOM nodes rather than strings. Usually, you don't
159+
notice this restriction because when a page loads, the web browser parses HTML into the DOM automatically.
160+
161+
However it's important to keep this in mind when calling `$compile` yourself, because passing it a string
162+
will fail. Instead, use `angular.element` to convert a string to DOM before passing elements into
163+
Angular's `$compile` service.
164+
165+
HTML compilation happens in three phases:
166+
167+
1. {@link api/ng.$compile `$compile`} traverses the DOM and matches directives.
168+
169+
If the compiler finds than an element matches a directive, then the directive is added to the list of
170+
directives that match the DOM element. A single element may match multiple directives.
171+
172+
2. Once all directives matching a DOM element have been identified, the compiler sorts the directives
173+
by their `priority`.
174+
175+
Each directive's `compile` functions are executed. Each `compile` function has a chance to
176+
modify the DOM. Each `compile` function returns a `link` function. These functions are composed into
177+
a "combined" link function, which invokes each directive's returned `link` function.
178+
179+
3. `$compile` links the template with the scope by calling the combined linking function from the previous step.
180+
This in turn will call the linking function of the individual directives, registering listeners on the elements
181+
and setting up {@link api/ng.$rootScope.Scope#methods_$watch `$watch`s} with the {@link api/ng.$rootScope.Scope `scope`}
182+
as each directive is configured to do.
183+
184+
The result of this is a live binding between the scope and the DOM. So at this point, a change in
185+
a model on the compiled scope will be reflected in the DOM.
186+
187+
Below is the corresponding code using the `$compile` service.
188+
This should help give you an idea of what Angular does internally.
189+
190+
<pre>
191+
var $compile = ...; // injected into your code
192+
var scope = ...;
193+
194+
var html = '<div ng-bind="exp"></div>';
195+
196+
// Step 1: parse HTML into DOM element
197+
var template = angular.element(html);
198+
199+
// Step 2: compile the template
200+
var linkFn = $compile(template);
201+
202+
// Step 3: link the compiled template with the scope.
203+
linkFn(scope);
204+
</pre>
205+
206+
### The difference between Compile and Link
207+
208+
At this point you may wonder why the compile process has separate compile and link phases. The
209+
short answer is that compile and link separation is needed any time a change in a model causes
210+
a change in the **structure** of the DOM.
211+
212+
It's rare for directives to have a **compile function**, since most directives are concerned with
213+
working with a specific DOM element instance rather than changing its overall structure.
214+
215+
Directives often have a **link function**. A link function allows the directive to register
216+
listeners to the specific cloned DOM element instance as well as to copy content into the DOM
217+
from the scope.
218+
219+
<div class="alert alert-success">
220+
**Best Practice:** Any operation which can be shared among the instance of directives should be
221+
moved to the compile function for performance reasons.
222+
</div>
223+
224+
#### An Example of "Compile" Versus "Link"
225+
226+
To understand, let's look at a real-world example with `ngRepeat`:
227+
228+
<pre>
229+
Hello {{user}}, you have these actions:
230+
<ul>
231+
<li ng-repeat="action in user.actions">
232+
{{action.description}}
233+
</li>
234+
</ul>
235+
</pre>
236+
237+
When the above example is compiled, the compiler visits every node and looks for directives.
238+
239+
`{{user}}` matches the {@link api/ng.$interpolate interpolation directive}
240+
and `ng-repeat` matches the {@link api/ng.directive:ngRepeat `ngRepeat` directive}.
241+
242+
But {@link api/ng.directive:ngRepeat ngRepeat} has a dilemma.
243+
244+
It needs to be able to clone new `<li>` elements for every `action` in `user.actions`.
245+
This initially seems trivial, but it becomes more complicated when you consider that `user.actions`
246+
might have items added to it later. This means that it needs to save a clean copy of the `<li>`
247+
element for cloning purposes.
248+
249+
As new `action`s are inserted, the template `<li>` element needs to be cloned and inserted into `ul`.
250+
But cloning the `<li>` element is not enough. It also needs to compile the `<li>` so that its
251+
directives, like `{{action.description}}`, evaluate against the right {@link api/ng.$rootScope.Scope scope}.
252+
253+
254+
A naive approach to solving this problem would be to simply insert a copy of the `<li>` element and
255+
then compile it.
256+
The problem with this approach is that compiling on every `<li>` element that we clone would duplicate
257+
a lot of the work. Specifically, we'd be traversing `<li>` each time before cloning it to find the
258+
directives. This would cause the compilation process to be slower, in turn making applications
259+
less responsive when inserting new nodes.
260+
261+
The solution is to break the compilation process into two phases:
262+
263+
the **compile phase** where all of the directives are identified and sorted by priority,
264+
and a **linking phase** where any work which "links" a specific instance of the
265+
{@link api/ng.$rootScope.Scope scope} and the specific instance of an `<li>` is performed.
266+
267+
<div class="alert alert-warning">
268+
**Note:** *Link* means setting up listeners on the DOM and setting up `$watch` on the Scope to
269+
keep the two in sync.
270+
</div>
271+
272+
{@link api/ng.directive:ngRepeat `ngRepeat`} works by preventing the compilation process from
273+
descending into the `<li>` element so it can make a clone of the original and handle inserting
274+
and removing DOM nodes itself.
275+
276+
Instead the {@link api/ng.directive:ngRepeat `ngRepeat`} directive compiles `<li>` separately.
277+
The result of the `<li>` element compilation is a linking function which contains all of the
278+
directives contained in the `<li>` element, ready to be attached to a specific clone of the `<li>`
279+
element.
280+
281+
At runtime the {@link api/ng.directive:ngRepeat `ngRepeat`} watches the expression and as items
282+
are added to the array it clones the `<li>` element, creates a new
283+
{@link api/ng.$rootScope.Scope scope} for the cloned `<li>` element and calls the link function
284+
on the cloned `<li>`.
285+
286+
287+
288+
### Understanding How Scopes Work with Transclused Directives
289+
290+
One of the most common use cases for directives is to create reusable components.
291+
292+
Below is a pseudo code showing how a simplified dialog component may work.
293+
294+
<pre>
295+
<div>
296+
<button ng-click="show=true">show</button>
297+
298+
<dialog title="Hello {{username}}."
299+
visible="show"
300+
on-cancel="show = false"
301+
on-ok="show = false; doSomething()">
302+
Body goes here: {{username}} is {{title}}.
303+
</dialog>
304+
</div>
305+
</pre>
306+
307+
Clicking on the "show" button will open the dialog. The dialog will have a title, which is
308+
data bound to `username`, and it will also have a body which we would like to transclude
309+
into the dialog.
310+
311+
Here is an example of what the template definition for the `dialog` widget may look like.
312+
313+
<pre>
314+
<div ng-show="visible">
315+
<h3>{{title}}</h3>
316+
<div class="body" ng-transclude></div>
317+
<div class="footer">
318+
<button ng-click="onOk()">Save changes</button>
319+
<button ng-click="onCancel()">Close</button>
320+
</div>
321+
</div>
322+
</pre>
323+
324+
This will not render properly, unless we do some scope magic.
325+
326+
The first issue we have to solve is that the dialog box template expects `title` to be defined, but
327+
the place of instantiation would like to bind to `username`. Furthermore the buttons expect the
328+
`onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the
329+
widget. To solve the mapping issue we use the `locals` to create local variables which the template
330+
expects as follows:
331+
332+
<pre>
333+
scope: {
334+
title: '@', // the title uses the data-binding from the parent scope
335+
onOk: '&', // create a delegate onOk function
336+
onCancel: '&', // create a delegate onCancel function
337+
visible: '=' // set up visible to accept data-binding
338+
}
339+
</pre>
340+
341+
Creating local properties on widget scope creates two problems:
342+
343+
1. isolation - if the user forgets to set `title` attribute of the dialog widget the dialog
344+
template will bind to parent scope property. This is unpredictable and undesirable.
345+
346+
2. transclusion - the transcluded DOM can see the widget locals, which may overwrite the
347+
properties which the transclusion needs for data-binding. In our example the `title`
348+
property of the widget clobbers the `title` property of the transclusion.
349+
350+
351+
To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An
352+
isolated scope does not prototypically inherit from the child scope, and therefore we don't have
353+
to worry about accidentally clobbering any properties.
354+
355+
However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget
356+
isolated scope then it will not be able to bind to anything. For this reason the transcluded scope
357+
is a child of the original scope, before the widget created an isolated scope for its local
358+
variables. This makes the transcluded and widget isolated scope siblings.
359+
360+
This may seem to be unexpected complexity, but it gives the widget user and developer the least
361+
surprise.
362+
363+
Therefore the final directive definition looks something like this:
364+
365+
<pre>
366+
transclude: true,
367+
scope: {
368+
title: '@', // the title uses the data-binding from the parent scope
369+
onOk: '&', // create a delegate onOk function
370+
onCancel: '&', // create a delegate onCancel function
371+
visible: '=' // set up visible to accept data-binding
372+
},
373+
restrict: 'E',
374+
replace: true
375+
</pre>
376+

0 commit comments

Comments
 (0)