Update scope half way through a scope method - angularjs

EDIT: I've put together a JSFiddle: http://jsfiddle.net/U3pVM/18137/
I want the black to go up AND THEN the green to slide across... I do not know how to achieve this... Read on for more information:
Side note: The code is in Typescript (re: () => {} )
The following context shows a web page that has an overlaying div, and a couple of divs behind it. It also denotes the kind of animation that is occuring via a css class.
I have the following code (see references to diagram):
$scope.Resizing = true; // This prevents animations from happening (as a css class is linked up to it (see below))
$scope.MoveToPage( col, row ); // 2
$scope.$applyAsync(() => { // Update the view to remove the class and then start sliding up the overlay div
$scope.FirstPageTop = -$scope.PageMap.WindowInfo.Height; // 3
$scope.Resizing = false; // Re-enable animations // Finish
});
What it is supposed to do is:
When Resizing is true, it removes a css class that animates stuff.
It then runs the MoveToPage method that makes a position left equal a new number.. usually it would animate to the new position but in this instance i don't want it to (thus the Resizing attribute).
We can see the above expressed in my html:
<div class="column-wrap"
ng-class="{ 'column-wrap-animation' : !Resizing }"
ng-style="{ 'width': PageMap.ContainerWidth + 'px',
'left': PageMap.ColumnWrap.Left + 'px'}">
The procedure then applies the scope asynchronously (because if i use a simple apply, it throws an error) and as a callback to this method, I am then doing pulling an overlay off of the top of the page... and then setting Resizing back to false to re-apply animations.
The problem:
I need to slide off an overlay AFTER positioning the content underneath it. What is actually happening is, it slides the overlay off of the top, while the under page animates to where i've told it to go. How do i get around this css animation digestion problem...
Perhaps an even simpler question is, why might the class not be being removed when Resizing is set to false and actively i am telling it to update the scope?
Explaining my context:

You can apparently use setTimeout to deal with the $scope.$apply issue:
http://jsfiddle.net/U3pVM/18138/
$scope.Resizing = true;
$scope.MoveToPage( col, row );
setTimeout( function() {
$scope.FirstPageTop = -$scope.PageMap.WindowInfo.Height;
$scope.Resizing = false;
$scope.$apply() // This is important
}, 1);

Related

Angular scope variable update not reflected in UI

We are working on an HTML page which makes use of a Bootstrap tooltip on a certain <span> tag. For those who have not heard of tooltip, it is a popup of sorts which appears when hovering over the element to which it is attached. Here is a screenshot showing the <span> in question, and what happens on hover:
The premise behind adding the tooltip was that in the event that we truncate the text, the tooltip would provide an option for viewing the entire text.
However, we would now like to condtionally show the tooltip only when there is no ellipsis in the text. We defined the tooltip-enable property in the <span>:
<span uib-tooltip="{{someName}}" tooltip-placement="right" tooltip-enable="{{showToolTip}}">{{someNameShortened}}</span>
The key thing here is tooltip-enable="{{showToolTip}}", which binds the property to a scoped variable in the controller for this page. And here is the relevant (and abbreviated) controller code:
mainApp.controller('repoListController',['$scope', '$rootScope', ...,
function($scope,$rootScope, ...) {
$scope.showToolTip = false;
var repositoryList= function(){
repositoryService.getRepositoryList(function(data) {
var repoList = data;
repoList.shortenedDisplayName = repositoryService.getShortRepoName(repoList.repoName, DISPLAY_NAME_MAX_LENGTH);
// if the repository's name be sufficiently large (i.e. it has an ellipsis)
// then show the tooltip. Otherwise, the default value is false (see above)
if (repoList.repoName.length > DISPLAY_NAME_MAX_LENGTH) {
$scope.showTooltip = true;
}
});
}
repositoryList();
}]);
Based on the research I have done, the common solution for why a change to a scoped variable is not reflected in the UI is to run $scope.$apply(), or some variation on this. Running apply(), as I understand it, will tell Angular JS to do a digest cycle, which will propagate changes in the scope to the UI. However, trying to do an apply() from the code which toggles showToolTip resulted in errory. I inspected the value of $scope.$root.$$phase while running the code which updates the showToolTip variable, and the phase was digest.
So now I am at a loss to explain this. If the code is already in a digest, then why would changes not be reflected in the UI? Also, if the code is already in digest, then how could I force Angular to sync the changes to the UI?
Two things need fixing...
Don't use string interpolation for your boolean showToolTip
<span uib-tooltip="{{someName}}" tooltip-placement="right"
tooltip-enable="showToolTip">{{someNameShortened}}</span>
JavaScript variables / properties are case sensitive. In your getRepositoryList handler, you have $scope.showTooltip. It should be $scope.showToolTip (two capital "T"s)
Crappy Plunker demo ~ http://plnkr.co/edit/W7tgJmeQAJj0fmfT72PR?p=preview

CSS keyframe animations only running once in Angular app

Updated: In short, my problem seems to be that the DOM stops updating whenever I am calling a function which switches from one state to another in the application.
More interestingly though, this actually only happens the second time this function is called from one particular element.
For specific details, I'm developing an single page app in AngularJS 1.2.28 and one I've stripped my html template, display-widgets.html to contain only 3 elements as follows:
<p data-ng-click="widgetCtrl.onWidgetSelected(widgetCtrl.model.widgetData[0].widget)">widget 1</p>
<p data-ng-click="widgetCtrl.onWidgetSelected(widgetCtrl.model.widgetData[0].widget)">also widget 1</p>
<p data-ng-click="widgetCtrl.onWidgetSelected(widgetCtrl.model.widgetData[1].widget)">widget 2</p>
<p>{{widgetCtrl.interstitialMode}}</p>
The controller looks something like as follows:
widgetShop.widgetCtrl.prototype.showWidgetHome = function (widget) {
this.state.go('widgetHome', {
widgetId: widget.widgetID
});
};
widgetShop.widgetCtrl.prototype.setupInterstitial = function () {
this.interstitialMode = true;
};
widgetShop.widgetCtrl.prototype.onWidgetSelected = function(widget) {
this.setupInterstitial();
this.showWidgetHome(widget);
};
The intended functionality is that after the widget is selected, onWidgetSelected() is called passing the selected widget as a parameter where the animation variable is updated (with the intention to add class with ng-class in the DOM) with setupInterstitial() before another function is called to switch the state. Some interaction will occur on this state, (e.g. add widget to basket) and once complete, the state is switched back to the display-widgets state.
I've stripped the html down here to be just p tags for testing but I've found if you select the same widget(p tag) again, the element is NOT updated in the page as before, and more interesting still is that no DOM updates occur at all. Console logs and alerts show me that angular has updated the correct variables in the controller but the state transition seems to prevent any DOM updates occurring. Selecting the widget 2 will update the DOM as expected, but as with the first widget, this will not occur more than once. Selecting 'also widget 1' after selecting widget 1 will not update the DOM as though it were the second time it were selected, but selecting this before widget 1 will have the same affect as selecting widget 1 first.
Does anybody know if this is a specific issue with browsers not performing animation again because it is a single page app? Or is there likely to be an issue elsewhere in my code? I haven't been able to find anything via google or stack overflow with people having a similar issue.
Sounds like once you've added the class, it is never removed again.
Once the class is added, the animation starts. When you add it again nothing changes because it already had that class and all the styling was already applied.

AngularJS mouse leave trigger misses

On my web page, I have an array of
<div ng-repeat='img in imgList'>
<div class='img_container' ng-mouseover="show(img)" ng-mouseleave="hide(img)">
<div>one thumbnail</div>
<div class='overlay_edit' ng-show='img.isShowEdit'></div>
</div>
</div>
// from controller
$scope.hide = function hide(img){
img.isShowEdit = false;
}
$scope.show = function show(img){
if(img.metas != undefined && img.metas.length > 0){
// a few lines of codes to use img.metas to
// format the edit div block, omitted for simplicity
// but it does involve calling a REST service call
// to retrieve all meta properties.
img.isShowEdit = true;
}
}
The overlay edit div shows when mouse enters the container div and hides when mouse leaves the container div.
The problem I have is that I see lots of ghost overlays when mouse moves too quickly across many containers.
What would be the best way to tackle this problem?
Edited to add additional info and correct errors.
The problem is that the show() takes some time to complete. If ng-mouseleave trigger takes place before show() completes, ng-mouseleave's trigger has no net effect. When I moved the "img.isShowEdit = true;" to the beginning of show() body it solved the ghost issues. Thanks to Narek for pointing out the show() function as the possible source of issue.

angularJS doesn't scroll to top after changing a view [duplicate]

This question already has answers here:
Changing route doesn't scroll to top in the new page
(18 answers)
Closed 8 years ago.
For example:
A user scrolls down on view A;
Then the user clicks on a link, which takes the user to view B;
The view is changes,
but the user's vertical location remains lthe same, and must scroll manually to the top of the screen.
Is it an angular bug?
I wrote a small workaround that uses jquery to scroll to the top; but I don't find the correct event to bind it to.
edit after seeing the comment:
How and WHEN do i pull myself to the top? i'm using jquery but the $viewContentLoaded event is too soon (the method runs, but the page doesn't scroll at that time)
The solution is to add autoscroll="true" to your ngView element:
<div class="ng-view" autoscroll="true"></div>
https://docs.angularjs.org/api/ngRoute/directive/ngView
Angular doesn't automatically scroll to the top when loading a new view, it just keeps the current scroll position.
Here is the workaround I use:
myApp.run(function($rootScope, $window) {
$rootScope.$on('$routeChangeSuccess', function () {
var interval = setInterval(function(){
if (document.readyState == 'complete') {
$window.scrollTo(0, 0);
clearInterval(interval);
}
}, 200);
});
});
Put it in your bootstrap (usually called app.js).
It's a pure javascript solution (it's always better not to use jQuery if it's easy).
Explanation: The script checks every 200ms if the new DOM is fully loaded and then scrolls to the top and stops checking. I tried without this 200ms loop and it sometimes failed to scroll because the page was just not completely displayed.
It seems that you understand why the problem is happening based on #jarrodek's comment.
As for a solution, you could either follow #TongShen's solution of wrapping your function in a $timeout or you can put the function call within the partial that you're loading.
<!-- New partial-->
<div ng-init="scrollToTop()">
</div>
If you view change is fired after a click event, you could also put the function call on that element. Just comes down to timing though. Just depends on how things are set up.

Conditional Animation for AngularJS

Trying to get conditional animation to work. This method works for just two classes (left and right) but gets weird with three classes.(sometimes it will fade, sometimes it will use left/right) It also gets unwieldy with more classes. Any tips?
I have whatever I want to animate (in this case it would be ng-view) with these classes already defined.
ng-class="{animateLeft: goingLeft, animateRight: goingRight, fadeIn: fading}"
I add these functions to ng-click() on my navigation buttons.
$scope.goRight = function(){
$scope.goingRight = true;
$scope.goingLeft = false;
$scope.fading = false;
};
$scope.goLeft = function(){
$scope.goingRight = false;
$scope.goingLeft = true;
$scope.fading = false;
};
$scope.fadeIn = function(){
$scope.goingRight = false;
$scope.goingLeft = false;
$scope.fading = true;
};
I figured out what was being weird. This method actually does work. You just need to understand how angular animates things step by step.
To animate something in angular you assign it 4 CSS classes.
1)The state it should start in when it ENTERS. (e.g. off to the side and invisible)
2)The state it should end in when it ENTERS. (e.g. going to the middle and visible)
3)The state it should start in when it EXITS. (e.g. start from the middle and visible)
4)The state it should end in when it EXITS. (e.g. go to the side and invisible)
This means that there are TWO objects on "stage" as the same time as the animation is occurring (one is leaving while the other is entering). When an object enters the stage, it's already programmed to leave in a certain way. When the other objects comes onto the stage, that objects leaves.
My problem was that I wanted my view to conditionally LEAVE as well as conditionally ENTER. I could call the ENTER animation properly but because the LEAVE conditional was already set BEFORE knowing how I'd call enter the way it leaves cannot be changed.
I changed my code so that the pages animate into the stage based on a condition (e.g. animate in from arrow direction you click) but made them fade to 0 on leave instead of leave in the opposite direction. Previously the animations were weird because I would click on RIGHT, the page would animate in from the right, but then when I wanted to fade to another page the previous page would leave to the LEFT which was confusing.
Just remember that the page your animating get both its enter AND leave classes set when you call it.

Resources