Buttons 'flickering' in angular application using ng-if or ng-show - javascript

I've had this same issue in two different angular applications I've worked on and yet I have been unable to find any discussion of this problem - which makes me think perhaps I am missing something. Let's say I have a view of a 'task' which can be in a number of different states including 'pending', 'accepted' and 'completed'. Different action buttons will be shown depending on the state of the task, for example:
<button ng-if="task.status === 'pending'" ng-click="ctrl.acceptTask()">Accept</button>
<button ng-if="task.status !== 'accepted'" ng-click="ctrl.acceptTask()">Flag</button>
<button ng-if="task.status === 'accepted'" ng-click="ctrl.flagTask()">Complete</button>
The issue is that when the user clicks the accept button, for a brief period both of the buttons below will be displayed. It's as if angular is sequentially working through the DOM and for the brief period between ng-ifs, both the 'flag' and 'complete' button are displayed because only one has been updated. This occurs for ng-show as well.
Note, this is not an issue that can be solved with ng-cloak, which is only there to prevent a template being displayed before angular has done its magic.
Given I've encountered this issue on both of the two large angular applications I've worked on, it must be a common problem. Any suggestions as to how this is typically solved? (PS, the above HTML is just an example of what I mean, it's not my actual template.)

That is because ngIf completely removes and recreates the element in the DOM. When an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored. The scope created within ngIf inherits from its parent scope using prototypal inheritance. also,ngIf recreates elements using their compiled state.
Indeed having ngIf evaluate to false will remove the element. But only if it is not set to true immediately after, as angular only refresh the DOM (your HTML) when it has the opportunity to do so, i.e. after the current synchronous block of code.
Below code snippet does not have any effect:
$scope.task.status= 'pending'; <br/>
$scope.task.status= 'accept';
This will
$scope.task.status= 'pending';
// $timeout will wait for a digest cycle before executing the code
// Angular will remove the button from the DOM
$timeout(function() {
$scope.task.status= 'accept';
// Here a second digest cycle happen, angular will put the button back into the DOM
});
Please note that it might be due to your angular app emerges many watchers due to heavy DOM contents. I have made one simple plunker to replicate your scenario but not facing it. You can check and reduce watchers by adding available chrome plugin and/or many others way like this. Just google it and you can find a treasure of lots of fruitful information to improve watcher count of your app.
Ok now I got it what exactly you do require here:) See !! This is what angular docs told us "Note especially the powerful ng-switch that should be used instead of several mutually exclusive ng-shows." One of the good read on "When to use what!!!". So i hope flickering buttons will not happen if you use the ng-switch at those instances or alternatively we can achieve the scaffold of solution that is quite generic and works without worrying about current scope at all by using directive initialization function to inter-instances communication. See this in which i have applied class to multiple buttons in mutually exclusive way.

The below ng-if statements ensures that it will display one at a time. If you see it both at the same time it should be because something in your code is messing with angular digest cycle which delaying the two way binding of the fields and rendering of the buttons.
<button ng-if="task.status !== 'accepted'" ng-click="ctrl.acceptTask()">Flag</button>
<button ng-if="task.status === 'accepted'" ng-click="ctrl.flagTask()">Complete</button>

Related

How to disable Angularjs completely after the page is fully loaded

I want to disable all the functionalities provided by Angularjs, but only after the pages and all components have been loaded fully.For example "ng-click" should not work any more.
I tried to set the "ng-click" attr to null but, it still works when clicked.
Thank you
You can destroy angular app $scope that means it will disable only two way binding of scope variables using $scope.$destroy() method nothing more than that(If you want to disable two way binding on start up load then you need call $destroy() in $timeout).
But the event listener won't get disabled from angular app which are register while angular app is initialized on page. You can only achieve this by maintaining any flag (this is hacky way).
Here is Fiddle which demonstrate what i want to say.
Thanks.

Many instances of ngIf vs ngShow for complex UI and forms

I found a few questions regarding ngShow vs ngIf but nothing that addressed my use case all that well.
Alternative to ng-show/-hide or how to load only relevant section of DOM
what is the difference between ng-if and ng-show/ng-hide
When to favor ng-if vs. ng-show/ng-hide?
I understand the differences between the two directives but for an application with many conditional UI elements (content, toolbar buttons, dropdowns, form fields, etc - some of which are in ngRepeats) which is the better choice - ngShow or ngIf? As the user navigates the application and loads different content into the UI, these conditions will be evaluated so this will happen regularly as opposed to one time as it might with something like user preferences or permissions. Also, much of the content that is conditionally displayed contains {{bindings}}. The number of extra watches is my concern with ngShow while the large number of DOM manipulations concerns me with ngIf.
Is there a best practice or guidance for this type of situation? Is there some threshold where one makes sense over the other? Or is it simply a matter of trying both to determine the performance impacts of each (easier said than done)?
Always use ng-if if possible. Content which is not in the DOM must not be watched etc. if you have bindings. Therefore it performs better.
Use ng-show only if
you have complicated event handlers on DOM elements which can be hidden
the hide/show value changes extremely often (DOM manipulation is expensive)

ng-repeat loop more than the number of elements

I am trying to implement ng-repeat with ng-style. It works perfectly. However, I do not understand why my method is called more than the number of elements in my ng-repeat!
Do you know why?
To complete my explanation, I created a : JSFiddle
I think that when your html code is compiled, it executes ng-style directive even if there is no data (when items == null). After that your controller changes $scope.items, forcing other calls to $scope.getStyle().
I think if you put a ng-if="items != null" within ng-repeat, your function will be called only 5 times.
Solution looks fine. The reason for multiple calls is angular digest loop. You can read about it here: http://www.benlesh.com/2013/08/angularjs-watch-digest-and-apply-oh-my.html
It works by running the loop and looking if the values changed for the watches. When they stabilize it ends. You can have multiple passes of the event loop in angular app and that is pretty normal. The limit is set afaik for 10 iterations. If bindings do not stabilize then exception is thrown.
Additional reading, highly recommended:
http://teropa.info/blog/2013/11/03/make-your-own-angular-part-1-scopes-and-digest.html
Part Keep Digesting While Dirty is the answer to your question i believe.
So it is by design.

Angular 1.2.0-rc.3 Directive Priority Change Issue

So I have this code example that uses Angular 1.2 RC2 and everything works fine, you click on the handle to toggle the display of the content and the controller and directive have seperate scopes as intended:
http://plnkr.co/edit/e3XAZuhSMAxmkWzKKM39?p=preview
Now I upgraded to Angular RC3 yesterday and now the functionality does not work as it stands in the plunker, I get the error the specific requires generic which is can't find. Going through the change log, I though this might have to do with this breaking change:
$compile: due to 31f190d4, the order of postLink fn is now mirror opposite of the order in which corresponding preLinking and compile functions execute
To fix this they either suggest converting the post linking to a pre linking (which I can do since my post linking needs access to the scope which is not available in the pre linking) or to decrease the priority of the directive. So this plunker does that and functionality does work:
http://plnkr.co/edit/arP3aruH8HEdiwFg6mWp?p=preview
However there is a major issue and that is now because specific has a higher priority, the isolate scope that generic needs is no longer being created so now contentVisible is on the controller scope which is not good.
Now I could just move the scope: {} from the generic directive to the specific directive however it should be possible to use the generic directive by itself and if I did it would attached to whatever scope it is part of and not its own (which would make it impossible to have multiple instance of this directive, which is way it needs its own scope).
The only thing I can think of is to add a directive, called something like isoScope, make sure it has a very high priority, and have it define scope: {}. Then if I need to use generic by itself, I just have to make sure to also add the isoScope directive to make sure it has an isolate scope. Like this:
http://plnkr.co/edit/1NYHpUcPFWEbAzvkCeRH?p=preview
Now I am hoping there is a better way to accomplish what I am looking for without the isolateScope directive. Am I missing a way of that this without that?
UPDATED EXAMPLE
So here is another plunker that includes hopefully better examples of what I am trying to convey (still has virtually no styles but should not be needed to get the point across):
http://plnkr.co/edit/KtRMa1c9giDrhi1Rqyho?p=preview
I have 3 directives:
expander
notification
isolateScope
The expander directive only adds functionality to be able expander and collapse content, nothing more. This functionality is something that should be able to be used alone or with another directive (which is why it has a controller).
The notification directive is used to display notification however since we don't want to display the notifications all the time, we use it with the expander directive so that the user can toggle the display of the actually notifications (similar to how stackoverflow.com does it in the top left).
While I imagine the expander would most likely be used with another directive it should be possible to use alone and that is where the isolateScope directive comes into play. Since the expander directive adds data to the scope and you may want to have multiple expanders on the same page, it needs to have an isolate scope in order to work. Now on a users profile page you have have data like developer key and address that you don't really need to display all the time so lets have the user control that. I have the isolate scope to be able to control both of those independently because without the isolate scope, both of them would be on the same scope and be controlled by the same instance on contentVisible.
I just don't see anyway with how directives now run in 1.2.0 RC3 to be able to accomplish this without that isolateScope directive (though I would be happy to be proven wrong).
I have updated your code so that it does what I think you want (at a minimum this works the way your old code does, but under rc3 as you wanted): http://plnkr.co/edit/nsq4BGAih3lfNmS2mLP7?p=preview
But I made quite a few changes and a significant architectural change so let me know if this moves away from the spirit of what you're trying to achieve.
I think the gist of the issue was that your two directives (generic and specific) were tightly coupled around contentVisible which created a complex dependency that resulted in you having to very carefully manage invocation timing. My approach was to decouple the two directives- encapsulating contentVisible within generic. This allows generic and specific to instantiate fully independently. So you're not dependent on any invocation timing. And thus the directive priority change no longer has any impact on this code. So, one big win with the solution I propose is it should be robust against further changes by the Angular team.
Specifically, I moved the template in to the same directive (generic) as the controller which manages contentVisible . This way the controller that changes contentVisible lives on the same scope as the template which uses it.
Now specific just calls over to the required: generic controller to toggle visibility (effectively as a setter function).
I also moved the ng-class assignment into the template in order to encapsulate that change within one place (the template) and so you don't need jquery or a link:/compile: on generic.
This is a regression. A fix is in the works: https://github.com/angular/angular.js/issues/4431
I have problem, looks very close to your. So if anything will change want to be notified.
My task: I have contact, that could be shown in defferent ways (very common task), but difference between views is in templates, whereas help functions and preparations are same, so I need generic directive for all views.
What I found:
1. in rc2 it works fine in rc3 unstable
2. in rc3 it could work same only when template is inline, but not when it is templateUrl (even if cached)
So I created two planks rc2 version and rc3 version.
Hope this will help.

I don't understand Angular data binding at all

I've spent several hours trying minor variations of this code, and I don't understand why one works and the other doesn't.
Here's the scenario: I'm trying to present a list of registered users (which I'm getting with a simple database query that returns just a few columns), then when one user's name is clicked on, I'll retrieve more information about that user from the database, and present it in a different view. At the moment, I'm doing this with regular <a> elements with an ng-click directive that sets a value called currentid. Elsewhere in my code, I use $watch() to send out a new database query whenever currentid changes. That part seems to be working (I see the console.log() output from my watch callback, and the database query is spitting out the right data)... but sometimes currentid changes, and sometimes it doesn't, and I cannot figure out why.
A jsfiddle where it doesn't work: http://jsfiddle.net/aLcRL/11/
A jsfiddle where it DOES work: http://jsfiddle.net/aLcRL/12/
(Click on the "rmunn" and "admin" links in the table: the "Currrent ID" value below should update. And please pardon the almost total lack of CSS; I'm a coder, not a graphics designer ("Dammit, Jim!"), and it's also very late in my timezone right now so I have no motivation to pretty this up. It's functional, it demonstrates the problem, and I'm going to leave it at that.)
The only difference between these two is that one is binding to currentid, and the other is binding to vars.currentid. Now, I understand why binding to currentid wouldn't work in the case of a parent and a child scope (the child's value would overshadow the parent's value); since I'm coming from a Python background, this makes sense to me (it's similar to how Python's instance namespaces can shadow the namespaces of anything defined on the class). But in this jsfiddle, I'm only using one controller -- aren't all these variables in the same scope?
I've been beating my head against this all day, and the several Stack Overflow answers I've tried to read (like How does data binding work in AngularJS? for example) have just left me even more confused: $apply() and $digest() and scopes, oh my! So if anyone can give me a nice simple beginner's guide to scopes in Angular (or point me to an already-written one that I've missed), I'd be very grateful.
"I've learned Clojure and Haskell, I can learn a simple Javascript framework," I thought. "This'll be easy." Boy, was I wrong. :-)
You are running into an issue where ng-repeat creates a child scope.
<tr ng-repeat="user in data" blarg="{{user.id}}">
<!-- currentid here is part of the ng-repeat child scope
and not your root scope -->
<td>{{user.userName}}</td>
<td>{{user.email}}</td>
</tr>
If you need to access the parent scope you can use $parent.currentid
{{user.userName}}
You need to use $apply() whenever you modify a value outside of the angular world. For example using setTimeout() or a jQuery Plugin. Calling $apply() alerts Angular to reevaluate the scope to see if anything changed and update accordingly.

Categories