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.
Related
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.
In the below template, I would expect the script tag to never render, and the alert script to never execute. However it does.
<div ng-if="false">
<script>alert('should not run')</script>
Should not appear
</div>
This is causing us huge performance problems on mobile devices as we wrap large DOM and directive structures in ng-ifs with the expectation they will not render when the condition is false.
I have also tested ng-switch which behaves in the same manner.
Is this expected behaviour? Is there a way to avoid the unnecessary render?
JSFiddle
It may seem backward, but ngIf deals more with the removal of DOM, rather than the addition. Before the controller finishes instantiating, the DOM still exists. This is generally a good thing, and allows you to have graceful degradation for users without JS (or, alternatively, an initial loading state).
If you don't want the inner DOM to render, place it in a directive (either its own directive, or via ng-include) or in a view.
Example 1 (understanding why the script runs):
To help yourself understand the flow a bit better, you can update the example to instead be:
<div ng-if="false">
{{"Should not appear"}}
<script>alert('should not run')</script>
</div>
https://jsfiddle.net/hLw0nady/6/embedded/result/
You will notice that when the alert pops up, Angular has not yet interpolated "Should not appear" (it appears in its braces). After you dismiss the alert, however, it disappears.
Example 2 (how to prevent the alert):
An example of hiding the code that "should not run" in a directive can be viewed here: https://jsfiddle.net/hLw0nady/4/
In this example, only if you replace ng-if="false" with ng-if="true" will you get your alert.
I think that false expression is not well converted to angular false.
I can prove this by setting:
<div ng-if="!true">
Which doesn't render text in current div.
Anyway, it executes alert, i suppose it is executed before angular runs, that's why.
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
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.
I've written a custom directive to update html controls depending on a condition.
Fiddle: http://fiddle.jshell.net/agvTz/212/
The custom directive requires ngModelController, $setViewValue has updated the model value but the html control still shows no value or bunch of spaces in case of textarea. I'd imagine this has nothing to do with the digest cycle since the scope value is updated. Any and all kinda help is appreciated.
As written in the documentation, $setViewValue
does not trigger a $digest.
Therefore, you need to trigger the update via $render() method.