Inconsistent execution order of link functions for two nested directives - javascript

We are facing some weird behaviour in the execution of link functions of 2 nested custom AngularJS directives:
1/ The first time (or each time after a hard page refresh) the linking function of the outer directive is executed before the linking function of the inner directive --> this is how we expect/want it, since parameters are passed from the outer to the inner directive.
2/ But then, each time the directive is used again, eg. after navigating away from the view and returning to it (without ever refreshing the page), the execution of the linking functions gets reversed: i.e. the linking function of the inner directive is executed before the linking function of the outer directive.
This, obviously, results in errors, since params passed from the outer directive to the inner directive (which are used in the link function) are not yet existing.
We are puzzled by this problem for quite some time now, so we hope there are some bright minds out there that may be able to help us out here :-) Tx in advance!

Have you tried setting the priority value of the directives?
It is easily described in the priority chapter of this Article

Related

AngularJS suspend $watch for internal updates

I have written a directive which has a two way binding to bring in the text specification for a flow chart which is used to generate the actual objects (steps and connections) inside the directive. I have a $watch set up inside the directive to allow the controller to send in a new specification (e.g. loading a new chart), but I also want to have the directive make changes to the specification in response to user action (e.g. deleting a connection) so that the controller can save the changed specification.
I have a function in the directive which converts the chart objects back into a text specification, but if I simply replace the specification scope variable with the updated value, the original watch sees a change (which it thinks might have come from the controller) and so reloads the chart from the specification. This has the effect of breaking things like dragging elements around because the elements are being removed from the DOM and replaced by new ones.
What I would like to be able to do is temporarily suspend the $watch while I make my internal changes, or in some other way avoid the watch from triggering when the directive makes changes to it, only going off when the controller makes changes from outside. I tried to unbind and rebind the watch around making the change, but because the actual checking happens elsewhere in the cycle that does not work. An alternative solution I could use is to have two variables passed between the controller and directive, one going each way, but that is somewhat inelegant. Any better suggestions would be welcome.
When I do this, usually my $watch looks something like this:
$scope.$watch('MyVar',function(newval,oldval) {
if (oldval == newval) return;
if (newval == $scope.internalval) return;
// process here
})
For precisely the reason you outline. I don't believe you can turn the $watch off, so before I make an internal update to the watched variable, I update a tracking version of the variable to make sure I don't get infinitely-recursing changes. It seems like a pain, but the watched variables are finite and I always use a setter function so the code updating the internal value is only written once

Understanding how to use 3rd-party event handlers in Angular

I'm creating a simple Angular app that uses Ace editor (via ui-ace) for on-screen text editing. I want a handler to run when the cursor changes which will update a model object when the cursor is in a certain location. However, I also want to be able to click a button to move the cursor to a particular location (and also update the model object if necessary). Here's a jsfiddle that demonstrates the idea.
http://jsfiddle.net/fpzknzej/3/
The model object updates when the cursor is placed at the end of the word print on the second line. The problem is that the $scope.$apply() on line 30 will throw an in-progress error when the Move Cursor! button is pressed. However, without that line, the view bound to the model object will not update when the cursor is moved with the arrow keys.
My current understanding is that this is simply the wrong way to do this kind of thing and that I need to do something along the lines of wrapping the changeCursor event to operate solely in the angular world. However, I'm at a loss as to how to approach this task (custom directive seems to be the thing that comes up the most when searching for this type of thing?) and if there's a good resource for understanding how to interact with third-party event handlers within angular. Any pointers in the right direction would be appreciated.
Will try to make this as simple as possible.
All angular core event directives such as ng-click , ng-change etc will automatically call $apply() internally.
In your case with move cursor you start the digest with ng-click then have a bit of a circular issue where the external ace event triggered from within ng-click is also going to call $apply().
You really only want to call $apply() when events that change scope outside of the angular core need to update the view.
A short term workaround for your situation is to use $timeout() instead.
This will be added to the end of the current digest stack queue and will call $apply() once other digests are completed.
As for directive , yes, this code does belong in directive but you won't really be changing it's current structure ... just moving it to a different part of the app. For the short term that isn't going to change what is currently happening

Race conditions on directives

I have a directive that calculates the height and distance from the top of the window of an element that is passed in as an id to an attribute. The issue I'm running into is that I have several of these directives running (on elements that are attached to the same directive), and I'm running into a race condition where some of the directives that are lower down in the DOM are running before the ones that are higher up for whatever reason.
Is there a way I can make the bottom ones wait for the ones higher up via a promise or something? If so, how would I implement that? Is there a better way of handling this?
If you want code to be executed by your directive in the descending order (higer in the DOM comes first), you should either put that code in your directive's controller, or preLink methods.
The default link method is a shortcut to postLink which is executed in a ascending order.
That being said, I don't know your code so I am just making a guess, but it sounds like a service would be more appropriate than a directive, unless the calcul you're doing is done directly in that directive element, in which case using $element should be enough. But you should probably not pass an id or selector to a directive.

Issue creating isolated scope in Angular + UI Bootstrap

I have a simple table of data, created using AngularJS. One of the columns of the table is calculated from a function on the controller.
I have a button on the page that opens a new modal. When I open a modal using UI bootstrap, I get a new isolated scope (child of the root scope), as expected. If, however, I have an input text in the modal any key-presses in this text field automatically invoke functions on the parent scope - even though I can verify that the scope is isolated.
Here is a plunkr of the behavior: http://plnkr.co/edit/JzhxSDcSefDe04Psxq0w
As shown in the example, the third column of the table is calculated with a function called "ageNextYear". When the table is being rendered, this function is called many times as expected (and can be verified in the console log). If however, I open the modal and type some text in to the field, the "ageNextYear" function on the parent scope still gets called (type some text in the input field and watch the console log output).
I'm not sure whether this is intended behavior, or whether I'm doing something wrong. I have tried using dot notation on both scopes, and explicitly passing a new scope to $modal.open, but with no joy.
I can get around the problem (by creating a watchCollection on "people" and updating the table that way - which may be a better way of doing this overall) but wanted to validate whether others have seen this behavior also.
The issue you are experiencing is not related to the scope of the Modal Dialog. The issue is related to the use of a function within an ng-repeat expression. In general, using functions within expressions is a performance issue, but it's a much larger problem within an ng-repeat. according to This excellent article regarding common pitfalls of using scopes,
When using expressions in views or watchers, you should always remember that an expression is called every time AngularJS thinks it is needed. You will not get the best performance using functions, you might even miss some change events.
That means an expression…
within a ng-repeat will be called for each item separately. Additionally, this is used by the repeat directive to determine data changes.
Can be evaluated multiple times in one digest. This can happen when you're using multiple directives or additional scope watchers.
Can be evaluated even if the direct scope seems to be unchanged.
Containing a function will not be evaluated if the return value of the function changes, but only if the function definition has changed.
Your example causes 3 of these 4 to occur.
You repeat the function call for each object in your scope, 3 items = 3 calls to the function.
You add an additional watcher indirectly by calling the Modal Dialog.
Changes to the data in the Modal Dialog's scope causes evaluation of the scope of the controller containing the ng-repeat, even though the data within the ng-repeat didn't change (no way for it to know if the data changed until the $digest is called). Each change to the Modal causes the $digest, which causes another trip through the ng-repeat, and another call for each item in the ng-repeat.
In your case, the logic does not need to run every time the expression will be evaluated. It is better to compute and write the logic into the scope when the logic result has changed. This decouples the logic from the object and the view.
in summary,
Best practices:
DO NOT use functions in expressions.
DO NOT use other data besides the scope in an expression.
DO use $scope.$apply() when applying external data changes.
Simon,
I liked your question and I added watch on the scope and saw the digest cycle is getting called
$scope.$watch(function watchMe(scope) { console.log('Digest watched me!'); });
The following is the fork with the digest.
http://plnkr.co/edit/5PTO1uPFvmLrg7K9vzTm?p=preview
I donot know this is the reason but I think expressions inside the ng-repeat are calling the digest as it tries to evaluate expression on any event on that item.
I think we should evaluate expressions in the model and give the updated model to the ng-repeat to solve the issue.

Angularjs: Modifying inner html content provided by angular expression using angular directive

I have a simple angular directive that adds the string 'yes' to the inner html content of an element if the corresponding scope option variable is greater than 1. When the inner html is an angular expression, the original text is preserved but 'yes' is not added to it regardless of the scope option variable value. I've fixed this by wrapping the code in my directive that modifies the inner html text inside a timeout function, but is there is a proper way of doing this without using the timeout?
example of code: http://plnkr.co/edit/OhePRiHNJvNfKgNcrbnQ?p=preview
$timeout is fine here. What's it actually doing is forcing the HTML to finish rendering first before the Javascript in the directive calls .html(). Without the timeout, the HTML rendering may not yet have finished in the browser.
Using timeout here isn't a hack - you could set the time to 0 and it'll still work. Just having the $timeout/setTimeout there moves the code in it to the end of the browser event queue - after the HTML rendering work finishes, which is what you want. Why is setTimeout(fn, 0) sometimes useful? has more details about how and why it works.

Categories

Resources