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

docs($compile): add double compilation known issue #15392

Merged
merged 6 commits into from
Nov 23, 2016

Conversation

Narretz
Copy link
Contributor

@Narretz Narretz commented Nov 15, 2016

No description provided.

@Narretz Narretz added this to the Backlog milestone Nov 15, 2016
Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also put that in the compiler guide (with a short note here in the API docs that double-compilation is not supported).

* elements.
*
* This can cause unpredictable behavior, e.g. `ngModel` $formatters and $parsers will be
* attached again to the ngModelController. It can also degrade performance, as
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngModel $formatters and $parsers will be attached again to the ngModelController

Why? ngModel seems to only be compiled once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I was actually thinking of an issue with replace. I will change the example.

* attached again to the ngModelController. It can also degrade performance, as
* watchers for text interpolation are added twice to the scope.
*
* Double compilation should therefore avoided. In the above example, the better way is to only
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoided --> be avoided

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the better way --> a better way (?)

link: function(scope, element, attrs) {
var newEl = angular.element('<input ng-model="$ctrl.value">');
$compile(newEl)(scope); // Only compile the new element
element.append(newEl);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think usually it is preferrable to first append the element and then compile it, in order to have proper context (e.g. requiring parent directive controllers).

angular.module('app').directive('addOptions', function($compile) {
return {
link: function(scope, element, attrs) {
attrs.$set('addInput', null) // To stop infinite compile loop
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addInput --> addOptions

*
* In that case, it is necessary to intercept the *initial* compilation of the element:
*
* 1. give your directive the `terminal` property and a higher priority than directives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalize these sentences.

* that should not be compiled twice. In the example, the compiler will only compile directives
* which have a priority of 100 or higher.
* 2. inside this directive's compile function, remove the original directive attribute from the element,
* and add any other directive attributes. Removing the attribute is necessary, because otherwise the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to remove the attribute, since you pass the priority to $compile.

@Narretz
Copy link
Contributor Author

Narretz commented Nov 15, 2016

I've updated the example and moved it to the compiler guide, and added a short paragraph with a link to the compile api docs

@Narretz Narretz force-pushed the docs-compile-double branch from a622fa7 to af8f311 Compare November 15, 2016 17:36
return {
link: function(scope, element, attrs) {
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
element.on('mouseenter mouseout', function() {
Copy link
Member

@gkalpak gkalpak Nov 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opposite of mouseenter is mouseoutmouseleave 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand. Do you mean it should be mouseleave?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sorry, that's what I meant 😁

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still mouseout 😞

to a button with `ngClick` on it:

```
angular.module('app').directive('addMouseover', function($compile) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a big, fat comment or a danger alert that this is what you should NOT do. People tend to get confused when the docs show bad examples to be avoided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but we should have a general solution to this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried putting a div around it, but then the code blocks aren't rendered correctly. Seems to be an issue with the marked library: markedjs/marked#596

scope.$apply('showHint = !showHint');
});

attrs.$set('addMouseover', null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to remove this any more.

```

Another scenario is adding a directive programmatically to a compiled element and then executing
compile again.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, lets make it clear this is a BAD thing.

which have a priority of 100 or higher.
2. Inside this directive's compile function, remove the original directive attribute from the element,
and add any other directive attributes. Removing the attribute is necessary, because otherwise the
compilation would result in an infinite loop.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to remove the attribute, since you pass the priority to $compile.

return {
priority: 100, // ngModel has priority 1
terminal: true,
template: '<input ng-model="$ctrl.value">',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't want to have a template.

@Narretz Narretz force-pushed the docs-compile-double branch 2 times, most recently from 9bce926 to f97ed1f Compare November 22, 2016 16:49
@Narretz Narretz force-pushed the docs-compile-double branch from f97ed1f to 53bef98 Compare November 22, 2016 16:49
@Narretz
Copy link
Contributor Author

Narretz commented Nov 22, 2016

I've updated the PR @gkalpak PTAL

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of typos.
LGTM otherwise 👍

return {
link: function(scope, element, attrs) {
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
element.on('mouseenter mouseout', function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still mouseout 😞

2. Inside this directive's compile function, add any other directive attributes to the template.
3. Compile the element, but restrict the maximum priority, so that any already compiled directives
(including the `addOptions` directive) are not compiled again.
4. In the link function, link the compiled element with the element's scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing period at the end.

function on the directive element. In the following **faulty example**, a directive adds a mouseover behavior
to a button with `ngClick` on it:

```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be ```js (here and below) or does marked autodetect JS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always thought it auto-detects

to a button with `ngClick` on it:

```
angular.module('app').directive('addMouseover', function($compile) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still prefer a comment above this line; something like:

// BAD - Don't do this

And an equivalent comment in the recommended alternatives; e.g.:

// OK - You can do this

But I can leave without if you think it's too much. Up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave it as is, and we'll see how soon someone stumbles over this

@Narretz
Copy link
Contributor Author

Narretz commented Nov 22, 2016

I think the part about mouseout/leave is broken on Github's UI. You can see that it's mouseleave in the file at the latest fixup commit. It just shows wrongly in the "Files Changed" view. I can also not comment on it from the comment view.

@gkalpak
Copy link
Member

gkalpak commented Nov 23, 2016

There were two occurrences of mouseout. You have replaced the second one with mouseleave, but there is still one more (on line 397).

@Narretz
Copy link
Contributor Author

Narretz commented Nov 23, 2016

Omg ...

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (as soon as travis is happy)

@Narretz Narretz merged commit 69f59f2 into angular:master Nov 23, 2016
ellimist pushed a commit to ellimist/angular.js that referenced this pull request Mar 15, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants