Angularjs & dialog jQueryui - change {{msg}} only after the dialog closed - angularjs

I'm creating a jQueryui dialog in an angularjs directive.
The template of the directive is very simple:
<div class="myPopup">
<div>{{msg}}</div>
</div>
In the link method in the directive, I'm creating the dialog and register on openPopup event:
link: function (scope, element) {
var popupEl = $(element).find('.myPopup');
popupEl.dialog({ ... });
scope.$on('openPopup', function (event, args) {
scope.msg = "MY_MSG";
popupEl.dialog( "open" );
});
}
Pay attention that I'm populate scope.msg variable.
When the popup is open, I cannot see my msg.
I looked into the DOM and found that when the popup was opened, the {{msg}} was empty:
<div class="myPopup">
<div></div>
</div>
but when I closed the popup the DOM changed and the msg changed to scope.msg:
<div class="myPopup">
<div>MY_MSG</div>
</div>
Can someone please tell me what is wrong here and why it reacts like this?
Thanks!

As you have noted when you add scope.$apply() after your change to the scope.msg the value is updated correctly, but you get an exception of $apply already in progress.
If you read this article it explains why you need to use the $scope.$apply() method. Essentially AngularJS watches for changes to data on your scope, then it runs through a process of notifying the parts of your code that care about these changes. However sometimes parts of your code run while this process is in the middle of notifying, and if that happens and your code makes changes to the scope, it doesn't get seen until a little later - which is why you saw the message only update when the dialog closed.
So you need to trigger the $scope.$apply manually. Sometimes this can simply be to call that command after you make your change, but other times it will conflict and throw the 'in progress' exception.
The solution is to use the applyAsync method, which will queue your code to run in the next digest cycle; this is usually after a delay of about 10 milliseconds.
link: function (scope, element) {
var popupEl = $(element).find('.myPopup');
popupEl.dialog({ ... });
scope.$on('openPopup', function (event, args) {
// Trigger the change in the next digest cycle
scope.$applyAsync(function() {
scope.msg = "MY_MSG";
popupEl.dialog( "open" );
});
});
}

Related

ng-show directive takes too long to update the dom after trigger

The app has a controller, that uses a service to create an instance of video player. The video player triggers events to show progress every few seconds. When the video reaches to a certain point, I want to show a widget on top of the video player.
The view has the widget wrapped in ng-show directive.
It takes more then 60 seconds for the dom element to receive the signal to remove the ng-hide class after the event has been triggered and the values have been populated.
If I try to implement this using the plain dom menthod (like document.getElementById(eleId).innerHTML = newHTML), the update is instant.
What am I doing wrong? Here is the complete sequence in code:
Controller:
MyApp.controller('SectionController', ['$scope', 'PlayerService'], function($scope, PlayerService){
$scope.createPlayer = function() {
PlayerService.createPlayer($scope, wrapperId);
}});
Service:
MyApp.service('PlayerService', [], function(){
this.createPlayer=function(controllerScope, playerWrapper){
PLAYER_SCRIPT.create(playerWrapper) {
wrapper : playerWrapper,
otherParam : value,
onCreate : function(player) {
player.subscribe(PLAY_TIME_CHANGE, function(duration){
showWidget(controllerScope, duration);
})
}
}
}
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
}
}});
View:
<div ng-show="showWidget"> <div class="wdgt">{{widgetData.stuff}}</div> </div>
Solved it! $scope.$apply() did the trick.
My guess is, due to other complex logic ad bindings inside the app, there was a delay in computing the change by angular the default way.
#floribon Thanks for the subtle hint about "complex angular stuff".
The code inside the service function changed to:
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
$rootScope.$apply();
}}
Do you have complex angular stuff within your hidden view?
You should try to use ng-if instead of ng-show, the difference being that when the condition is false, ng-if will remove the element from the DOM instead of just hidding it (which is also what you do in vanilla JS).
When the view is simply hidden using ng-show however, all the watchers and bindings within it keep being computed by Angular. Let us know if ng-if solve your problem, otherwise I'll edit my answer.

directive that listens to document click event gets fired immediately after creation

I am working on an angular.js with angular-ui-router app.
I have a state in my app that imitates a kind of a pop up.
I have created a directive that is responsible to move the app to a different state when someone clicks outside of the popup element.
I have created a js.fiddle with my code:
http://jsfiddle.net/lobengula3rd/1e908559/8/
state 1 - is the popup state.
state 2 - is a different state.
I have a link in state 2 that when clicking it will make a state change to state 1 (the popup state):
<a ui-sref="state1">state1</a>
this is my directive code:
.directive('closeOnOuterClick', ['$document', '$state', function ($document, $state) {
return {
restrict: 'A',
link: function (scope, element, attributes) {
$document.on("click", function (evt) {
$document.off('click');
$state.go(attributes['closeOnOuterClick']);
});
element.on("click", function (evt) {
evt.stopPropagation();
});
}
};
}]
The problem is that for some reason the listener function of 'document.on' click event gets fired immediately after the creation of the popup element, thus throwing me out.
I don't understand why the function gets called immediately.
If i change in my controller (line 20) that on app start i will go to state1 instead of state2, it will work just fine for the first time.
I tried to change the link that changes the state to an ng-click event that will call a function in my controller, but that also didn't work.
what is the problem with my code?
I figured the problem.
I had angular-ui-router version 0.2.0. updating it to version 0.2.11 solved this issue for some unknown reason.
also if i change the link to be an ng-click instead of ui-sref that calls a function that eventually will change the state instead then you should also call the $event (angular object) stop propagation directly like this:
state1
Have you tried change the $document inside of the directive to element ? line 28
element.on("click", function (evt) {
$document.off('click');
$state.go(attributes['closeOnOuterClick']);
});
That works for me in the fiddle: http://jsfiddle.net/1e908559/10/

$watch on clientWidth isn't working

Inside of my directive I have this and it works only in the beginning, and whenever I resize later it never fires.
scope.$watch ->
cw: element[0].clientWidth
,(newValue, oldValue)->
if newValue.cw isnt oldValue.cw
console.log "changed"
,true
Your function looks right to me, but it's important to note that it won't fire on resize unless you manually trigger a digest on the resize event.
Without this, angular doesn't realise there's been an event that should trigger a digest, and so none happen.
Try adding this (remember to inject $window):
angular.element($window).bind('resize', ()->
scope.$apply();
)
Just like Jason Goemaat said, its because Watches are only checked on a $digest cycle
Depending on how the way your element is resized maybe this example may help you.
Here is a menu that expands it self when pressed on the arrow.
<div class="menu" ng-class="{'expanded' : main.expanded}">
<div class="menu-handler" ng-click="main.expanded = !main.expanded">
</div>
</div>
The menu is expanded by a ng-click that makes a change on the scope, in this case its a boolean var that acts as a flag. As the opening of the menu is made throught the change in the scope it calls the $digest cycle iterating through all $watch.
scope.$watch(function() {
return element[0].clientWidth;
}, function(oldValue, newValue) {
console.log(oldValue, newValue);
});
You can view the full example in this JSfiddle
https://jsfiddle.net/owenbcal/zf17s0mL/
Hope i was helpfull.
the way I know about watching this kind of values is making a function that is fired once the value changes so then I watch that function :
in the service:
this.getDataOut = function () {
return self.dataOut;
};
in the controller:
$scope.$watch(dataService.getDataOut, function() {
$scope.dataOut = dataService.dataOut;
});

Clearing an interval when an element housing an angular directive is removed

I've built a simple directive that adds a javascript-based loading animation. It is operating with a window.setInterval() loop. This works great, but when loading is complete, I use ngSwitch to swap in my content, which removes the element housing the loading directive attribute from the page.
Ideally, I'd like to watch for this change and clear my interval so the animation calculations are not still running in the background. I have tried watching a custom function that evaluates the presence of the element on the page. I know the function works at detecting this, but it seems timing is an issue -- namely, as far as I can tell, the $watch itself is cleared when the directive attribute's element leaves the page. My $watch'ed expression therefore never detects a change and never calls its callback that clears the animation interval function.
Is there a recommended pattern for dealing with this type of situation?
Relevant snippet from my template:
<div ng-switch on="dataStatus">
<div ng-switch-when="loading">
<div loading-spinner></div>
</div>
<div ng-switch-when="haveData">
<!-- data dependent on content we were loading -->
</div>
</div>
Simplified version of my directive:
myModule.directive('loadingSpinner', function () {
var updateMySweetAnimation = function (element) { /* ... */ };
return {
link: function (scope, iElement, iAttrs) {
var spinner = window.setInterval(function () {
updateMySweetAnimation(iElement);
}, 100);
scope.$watch(function () {
return $(document).find(iElement).length;
}, function (present) {
if (!present) {
clearInterval(spinner);
}
});
}
};
});
When the element is cleared from the page by ng-switch, two things should happen:
The scope created for ng-switch-when, the element with your directive on, is destroyed. This kills your $watch and generates a $destroy event across the scope that you can watch with scope.$on('$destroy', ...).
The element is removed from the DOM. This generates a separate destroy event that you can watch with iElement.on('$destroy', ...).
They should happen in this order, looking at the latest stable release (1.0.8 - https://github.com/angular/angular.js/blob/v1.0.8/src/ng/directive/ngSwitch.js), so your scope and thus your watch should always be dead when the element is removed from the DOM.
You could avoid this problem by watching from the outer scope, where ng-switch is defined. Or you could watch dataStatus, the same condition as in your ng-switch, rather than looking for the results of the ng-switch seeing your condition change.
Both of these would probably work, but actually all you need to do, and in fact the normal pattern for this, is to just watch for one of the $destroy events and clean everything up there. As the interval feels more relevant to the view than the model, I would use the DOM event and replace your $watch with
iElement.on('$destroy', function(){
clearInterval(spinner);
});

Adding ng-change to child elements from linking function of directive

I created a directive that should add a ng-change directive dynamically to all child input tags:
myApp.directive('autosave', function ($compile) {
return {
compile: function compile(tElement, tAttrs) {
return function postLink(scope, iElement, iAttrs) {
var shouldRun = scope.$eval(iAttrs.autosave);
if (shouldRun) {
iElement.find(':input[ng-model]').each(function () {
$(this).attr("ng-change", iAttrs.ngSubmit);
});
$compile(iElement.contents())(scope);
console.log("Done");
}
}; //end linking fn
}
};
});
The problem that I have is that the ng-change directive isn't running. I can see it that its added to the DOM element BUT not executing when value changes.
The strange thing is that if I try with ng-click, it does work.
Dont know if this is a bug on ng-change or if I did somehting wrong.
Fiddle is with ng-click (click on the input) http://jsfiddle.net/dimirc/fq52V/
Fiddle is with ng-change (should fire on change) http://jsfiddle.net/dimirc/6E3Sk/
BTW, I can make this work if I move all to compile function, but I need to be able to evaluate the attribute of the directive and I dont have access to directive from compile fn.
Thanks
You make your life harder than it is. you do'nt need to do all the angular compile/eval/etc stuff - at the end angular is javascript : see your modified (and now working) example here :
if (shouldRun) {
iElement.find(':input[ng-model]').on( 'change', function () {
this.form.submit();
});
console.log("Done");
}
http://jsfiddle.net/lgersman/WuW8B/1/
a few notes to your approach :
ng-change maps directly to the javascript change event. so your submit handler will never be called if somebody uses cut/copy/paste on the INPUT elements. the better solution would be to use the "input" event (which catches all modification cases).
native events like change/input etc will be bubbled up to the parent dom elements in the browser. so it would have exactly the same to attach the change listener to the form instead of each input.
if you want to autosave EVERY edit that you will have an unbelievable mass of calls to your submit handler. a better approach would be to slow-down/throttle the submit event delegation (see http://orangevolt.blogspot.de/2013/08/debounced-throttled-model-updates-for.html ).
if you want to autosave EVERY edit you skip your change handler stuff completely and suimply watch the scope for changes (which will happen during angular model updates caused by edits) and everything will be fine :
scope.watch( function() {
eElement[0].submit();
});

Resources