Function inside setTimeout doesn't modify $scope variables / CSS properties - javascript

I'm battling with setTimeout(), unable to even grasp what the problem could be. I first thought that it was a scope problem but can't fix it.
I'd like to delay the hiding/unhiding of a DIV to let my CSS transition on opacity kick in but when I click the alert_button to fade then hide the alert, it only fades and I'm left with an invisible div. Delayed $scope.alert_token doesn't switch to 'true' and the opacity of my alert stuck on 1.
app.js :
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.alert_token = true // hide if true
$scope.alert_message = ""
$scope.p_name = ""
$scope.isInArray = function(arr, item) {
// IF ITEM IS ALREADY IN ARRAY
if (arr.indexOf(item) > -1) {
$scope.alert_token = !$scope.alert_token
$scope.alert_message = "Entry already exists"
setTimeout(function() {
document.getElementById("alert").style.opacity = "1"
}, 305)
}
else ...
}
$scope.submit = function(listType) {
if (listType == "player") {
$scope.isInArray(p_list, $scope.p_name)
$scope.p_name = ""
}
else ...
}
$scope.closeAlert = function() {
document.getElementById("alert").style.opacity = "0"
setTimeout(function() {
$scope.alert_token = !$scope.alert_token
}, 305)
}

Anything happening outside angular's knowledge is not updated to the DOM. In you case its setTimeout. Instead use $timeout.
......
.controller('myCtrl', function($scope, $timeout) {...
^^^^^^^^
//Other code
....
$scope.closeAlert = function() {
document.getElementById("alert").style.opacity = "0"
$timeout(function() {//$timeout
$scope.alert_token = !$scope.alert_token
}, 305)
}
Also since you are using angularJS, to update CSS properties I would recommend you to use ngClass and ngStyle

Related

Refresh checkbox in ng-repeat when data changes

html
<li ng-repeat="col in columns">
<span class="inputH">
<input type="checkbox" value="col.name" ng-if="col.default === true" checked
ng-click="onColSelect(col.name,$event)" id="column_{{$index}}">
<input type="checkbox" value="col.name" ng-if="col.default === false"
ng-click="onColSelect(col.name,$event)" id="column_{{$index}}">
</span>
<span class="textH">{{ 'leadOpportunityHeader.' + col.name | translate }}</span>
</li>
JS
$scope.onColumnSelectCancel = function () {
setTimeout(function () {
var cookieData = $cookieStore.get('selectedColumn');
$scope.unSelectedColoumns = cookieData;
angular.forEach($scope.columns, function (value, key) {
var flag = false;
for (var k = 0; k < cookieData.length; k++) {
if (value.name == cookieData[k]) {
flag = true;
}
}
if (flag == false) {
value.default = false;
flag = true;
}
});
console.log("new column", $scope.columns);
}, 100);
};
What I am really trying to do, is whenever onColumnSelectCancel() is called, I need to refresh all the checkboxes with their check and uncheck properties.My data is changing, but checkboxes state is still not changing. If I checked a checkbox and then I call onColumnSelectCancel() , now the checkboxes should change according to the source $scope.columns
I have applied $apply also, but it didn't worked.
setTimeout is not a compnent of angularjs . So, you have to manually push update using $scope.$apply
Like this
setTimeout(function() {
var cookieData = $cookieStore.get('selectedColumn');
$scope.unSelectedColoumns = cookieData;
$scope.$apply();
}, 100);
Otherwise you can use angular $timeout, here angular manages $apply internally.
Like this
$timeout(function() {
var cookieData = $cookieStore.get('selectedColumn');
$scope.unSelectedColoumns = cookieData;
}, 100);
You have to inject $timeout in your scope.
If your controller code is working fine then just inject $timeout in your controller and change the setTimeout(function () { }) to $timeout(function() {}) and that should work.
So your code will be now:
$scope.onColumnSelectCancel = function () {
$timeout(function () {
var cookieData = $cookieStore.get('selectedColumn');
$scope.unSelectedColoumns = cookieData;
angular.forEach($scope.columns, function (value, key) {
var flag = false;
for (var k = 0; k < cookieData.length; k++) {
if (value.name == cookieData[k]) {
flag = true;
}
}
if (flag == false) {
value.default = false;
flag = true;
}
});
console.log("new column", $scope.columns);
}, 100);
};
This is because setTimeout is an asynchronous execution and Angular is unaware of those changes being done inside that block so we have to tell Angular that something has changed.
$timeout is an Angular wrapper of setTimeout.
Like Anik mentioned, you can also use $scope.$apply which forces Angular to run the digest cycle which may fail at a point when the digest cycle is already in progress. So it is always safe to use $timieout instead.

How to ensure that a scope variable updates and binds before going to view angularjs

In my code, there is this scope variable called $scope.detailsPresent which basically just checks if there is data and displays a different page based on the result.
What happens is that when i do console.log($scope.detailsPresent) the value is correct based on if there is data which the value should be false or if there is no data the value should be true.
But the view doesnt bind the value yet so on the view the value is not yet updated hence it doesnt show the correct page. How do i ensure that the value is updated in the view ? I have tries $scope.$apply() but i get an error so is there anyway to do it?
my_controller.js
angular.module('my.controllers')
.controller('ReferralCtrl', function($scope, $rootScope, $window, $state, $q, $timeout, referralCasesGroupByCaseStatus, AuthenticationService, $ionicLoading) {
$scope.detailsPresent = {myVar: false};
$scope.showCaseStatusFromDashboard = function(number) {
$timeout(function() {
$scope.$applyAsync(function() {
$rootScope.fromDashboard = true;
})
}, 1000, true);
$scope.showCaseStatus(number);
}
$scope.showCaseStatus = function(number) {
if(changedNumber !== 0 && changedNumber !== number) {
changedNumber = number;
}
else {
if(changedNumber > 0) {
$scope.$applyAsync($scope.detailsPresent.myVar = true);
}
}
$timeout(function() {
$scope.$applyAsync(function() {
referralCasesGroupByCaseStatus.showCaseStatus(number, $rootScope.listingDetails).then(function(listingCaseStatus) {
$rootScope.listingByCaseStatus = angular.copy(listingCaseStatus);
if(listingCaseStatus == 0 || listingCaseStatus == undefined || listingCaseStatus == null) {
$scope.detailsPresent.myVar = true;
$scope.changeNumber = true;
$state.go('app.case_status')
}
else {
$scope.detailsPresent.myVar = false;
$scope.noMoreItemsAvailable = false;
$scope.changeNumber = true;
$state.go('app.case_status')
}
})
})
}, 1000);
}
my.html
<ion-view view-title="Case Status">
<ion-content>
<div ng-if="detailsPresent.myVar">
<ng-include src="template='no-listings'"></ng-include>
</div>
<div ng-if="!detailsPresent.myVar">
<ng-include src="'templates/case-listings.html'"></ng-include>
</div>
</ion-content>
</ion-view>
I have been on this for about 6days but no success in sight. Any help is deeply appreciated.
This isn't a full answer, but I have some suggestions. This would work better as a comment, but I don't have enough reputation yet.
Try switching your ng-if attributes to ng-show attributes. I realize this leaves the included elements in the DOM, but it might work for you if the performance is not seriously affected.
Also, the reason you get an error when calling $scope.$apply() is because you're already within a call to $apply(), or rather $applyAsync().
See this answer for more details on $apply()

Angular auto trigger specific directive in ng-repeat

I have an interesting situation.
I have a directive with isolate scope that generate list of numbers and the user can choose numbers like in lottery.
The problem i have is that i required minimum of 1 line, if the user pick only one line so when he click play i want to auto trigger the next directive in the ng-repeat to pick for him numbers, I made this plunker so you guys can understand better and help me.
http://plnkr.co/edit/vWGmSEpinf7wxRUnqyWq?p=preview
<div ng-repeat="line in [0,1,2,3]">
<div line line-config="lineConfig">
</div>
</div>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.lineConfig = {
guessRange: 10
}
$scope.lines = [];
$scope.$on('lineAdded', function(event, line) {
$scope.lines.push(line);
});
$scope.play = function() {
/// here i want to check if $scope.lines.length
//is less then one if yes then auto trigger the next
//line directive to auto do quick pick and continue
}
})
.directive('line', function() {
return {
restrict: 'A',
templateUrl: 'line.html',
scope: {
lineConfig: '='
},
link: function($scope, elem, attr) {
var guessRange = $scope.lineConfig.guessRange;
$scope.cells = [];
$scope.line = {
nums: []
};
$scope.$watch('line', function(lotLine) {
var finaLine = {
line: $scope.line
}
if ($scope.line.nums.length > 4) {
$scope.$emit('lineAdded', finaLine);
}
}, true);
_(_.range(1, guessRange + 1)).forEach(function(num) {
$scope.cells.push({
num: num,
isSelected: false
});
});
$scope.userPickNum = function(cell) {
if (cell.isSelected) {
cell.isSelected = false;
_.pull($scope.lotLine.nums, cell.num);
} else {
cell.isSelected = true;
$scope.lotLine.nums.push(cell.num);
}
};
$scope.quickPick = function() {
$scope.clearLot();
$scope.line.nums = _.sample(_.range(1, guessRange + 1), 5);
_($scope.line.nums).forEach(function(num) {
num = _.find($scope.cells, {
num: num
});
num.isSelected = true;
});
}
$scope.clearLot = function() {
_($scope.cells).forEach(function(num) {
num.isSelected = false;
});
$scope.line.nums = [];
}
}
}
})
You could pass the $index (exists automatically in the ng-repeat scope) - variable into the directive and cause it to broadcast an event unique for ($index + 1) which is the $index for the next instance.
The event could be broadcasted from the $rootScope or a closer scope that's above the repeat.
Then you could capture the event in there.
Probably not the best way to do it.
I can try to elaborate if anything is unclear.
EDIT
So I played around alittle and came up with this:
http://plnkr.co/edit/ChRCyF7yQcN580umVfX1?p=preview
Rather
Rather than using events or services I went with using a directive controller to act as the parent over all the line directives inside it:
.directive('lineHandler', function () {
return {
controller: function () {
this.lines = [];
}
}
})
Then requiring 'lineHandler' controller inside the 'line' directive - the controller being a singleton (same instance injected into all the line directives) - you can then setup that controller to handle communication between your directives.
I commented most of my code in the updated plnkr and setup an example of what I think you requested when clicking in one list - affecting the one beneath.
I hope this helps and if anything is unclear I will try to elaborate.

How to trigger a controller function after an Animation in Angular JS

I have come to a standstill in terms on threading together a sequence of Animations and then a controller action.
What I basically want to do is basically
1. click on a button/div, 2.Trigger an Animation, 3. Once animation is complete run a function in a controller that resets the button/div
I have completed steps 1 & 2 and just need to get the last bit done.
Here is the Button
<button ng-class="{'clicked':clicked, 'correct' : answer.answer == 'correct' }"
ng-click="clicked = true"
ng-repeat='answer in answers'
type="button"
class="btn btn-answers answer-animation">
{{ answer.es }}
</button>
Here is the animation
app.animation('.answer-animation', function(){
return {
beforeAddClass: function(element, className, done){
if (className === 'clicked') {
if( $(element).hasClass('correct') ){
$(element).addClass('animated bounce');
} else {
$(element).addClass('animated wobble');
}
}
else {
done();
}
}
};
});
And here is the last step the controller, I want the trigger the submitAnswer function inside this controller, after the animation has finished. The main bit is submitAnswer
app.controller('Game', function($scope, $http, $location, QA, Rounds ) {
//Reset all QA buckets
QA.reset();
$scope.round = 1;
$scope.playing = true;
QA.setUpGameData();
$scope.answers = QA.answers();
$scope.question = QA.question();
$scope.submitAnswer = function(question, answer){
if($scope.round <= Rounds) {
if(question.en === answer.en){
$scope.round++;
QA.setUpGameData();
$scope.answers = QA.answers();
$scope.question = QA.question();
if($scope.round === Rounds + 1){
$scope.playing = false;
$scope.message = 'Amazing well done!';
$scope.score = ($scope.round-1) * 1000;
}
}
else {
$scope.playing = false;
$scope.message = 'Sorry Wrong Answer :(';
$scope.score = ($scope.round-1) * 1000;
}
}
};
})
I have tried writing the ng-click in the HTML like so
ng-click="clicked = true;submitAnswer(question, answer)"
and then setting a $timeout on the submintAnswer function, but does really get the UX the app deserves.
Again ultimately I want a way to trigger the submitAnswer function in the controller after the animation is completed.
You can get the $scope of an element using,
var $scope = angular.element(element).scope();
Though there are some problems with syncing the scope if this happens.

AngularJS - same timer for all directive instances

What is a best "Angular Way" to implement the directive that will have a shared timer for all it instances?
For example I have a directive "myComponent" and on the page it appears many times.
Inside of the component, exists some text that blink with some interval.
Because of business requirements and performance considerations, I would like that there will be single "timeout" that will toggle the blink for all instances at once (after document is ready).
I thought about the writing some code within directive definition:
//Pseudo code
angular.module("app",[]).directive("myComponent", function($timeout){
$(function() { $timeout(function(){ $(".blink").toggle(); }, 3000); } );
return {
//Directive definition
};
});
Or by using some kind of service that will receive the $element and add remove class to it:
//Pseudo code
angular.module("app",[])
.service("myService", function($timeout){
var elements = [];
this.addForBlink = function(element) { elements.push(element) };
$(function() { $timeout(function(){ $(elements).toggle(); }, 3000); } );
})
.directive("myComponent", function(myService){
return {
compile:function($element){
myService.addForBlink($element);
return function() {
//link function
}
}
};
});
In my opinion the most elegant and efficient would be to combine both these approaches by specifying the logic of the directive in the very directive initialization function. Here is a scaffold of what I actually mean:
app.directive('blinking', function($timeout){
var blinkingElements = [];
var showAll = function() {
for(var i = 0; i < blinkingElements.length; i++){
blinkingElements[i].addClass("blinking");
}
};
var hideAll = function() {
for(var i = 0; i < blinkingElements.length; i++){
blinkingElements[i].removeClass("blinking");
}
};
var blink = function () {
$timeout(showAll, 500);
$timeout(function(){
hideAll();
if (blinkingElements.length > 0) {
blink();
}
}, 1000);
};
return {
link : function(scope, element, attrs){
blinkingElements.push(element);
if (blinkingElements.length == 1) {
blink();
}
element.on("$destroy", function(){
var index = blinkingElements.indexOf(element);
blinkingElements.splice(index, 1);
});
}
}
});
And here is the working demo.
Moreover you can inject some service that will be responsible for configuration (setting the intervals and / or class) or you can provide the configuration by passing an object directly to the attribute. In the latter case you can enable applying different classes for different elements, but you should think of some policy how to deal with situation, when the interval was set more than once.

Categories

Resources