I have a function 'highlightBookmark' that should change the background color of a list item after 2 seconds. But it doesn't work!!!
It changes 'li' background only if the function is called by click event. It doesn't change it automatically after time is out, even if it actually calls the function.
Here is my code:
Controller.js
//markers
$scope.markers = [
{
time: 9.5,
text: "Bookmark 1",
class: false
},
{
time: 106,
text: "Bookmark 2",
class: false
}
]
$scope.currentBookmark = -1;
function highlightBookmark(index) {
$scope.markers[index].class = true;
}
var interval = setInterval(checkTime, 100);
function checkTime(){
if(Math.floor(player.currentTime()) == 2){
highlightBookmark(1)
clearInterval(interval);
}
}
$scope.jumpTo = function (index) {
highlightBookmark(index);
}
The highlight function, takes in an integer, looks for object at that position and updates set 'class' parameter to true. Example, if I pass number 1 to the function, it will look for object at index 2 and set the 'class property' to be true.
Then, after 2 seconds I want to call the highlightBookmark function. IT IS CALLED but it doesn't update the class, thus the background doesn't update.
I call the same with click event and it works.
HTML file
<ul id = "bookmarkList">
<li ng-repeat="bookmark in markers" ng-class="{'active': bookmark.class}" ng-click="jumpTo($index)">{{bookmark.text}}</li>
</ul>
This li has the ng-class property that I want to change after 2 seconds.
Here is a link to a similar code I did on codepen. It changes button color on click, but doesn't change on setTimeout even if method is called
https://codepen.io/Octtavius/pen/wgzORv
Could somebody help with this simple issue?
The vanilla setInterval function doesn't update scope variables. Try with the $interval API by Angular:
var interval = $interval(checkTime, 100);
function checkTime(){
if(Math.floor(player.currentTime()) == 2){
highlightBookmark(1)
interval.cancel();
}
}
Notice clearInterval(interval) changes to interval.cancel()
Also don't forget to inject it as dependency.
Fair point by charlietfl: Also cancel the interval if the scope gets destroyed.
Place this inside your controller:
$scope.$on("$destroy", function( event ) {
interval.cancel( timer );
});
More info: https://docs.angularjs.org/api/ng/service/$interval
setInteral and setTimeout run outside of the angular digest cycle, so they will not be properly picked up by Angular.
Consider using the $timeout object in your controller -- this gives you the timeout functionality but allows angular to keep an eye on it.
You should consider use angular $timeout instead of setInverval or setTimeout.
Because:
This functions don't $digest the $scope variables;
$timeout in this case requires less memory to do the exactly same thing that $interval would do.
So, this part of your controller will look like this:
//markers
$scope.markers = [
{
time: 9.5,
text: "Bookmark 1",
class: false
},
{
time: 106,
text: "Bookmark 2",
class: false
}
]
$scope.currentBookmark = -1;
function highlightBookmark(index) {
$scope.markers[index].class = true;
}
$timeout(checkTime, 2000);
function checkTime(){
highlightBookmark(1);
}
$scope.jumpTo = function (index) {
highlightBookmark(index);
}
Remember to inject the $timeout as a dependency in your controller.
P.S.
This code will mark a default after 2 seconds, you don't give enough details so I can know what the player is doing. So if you want to improve this, give more details and we can make it happen.
You should consider use "controller as". Here is a link to John Papa's article about it. https://johnpapa.net/angularjss-controller-as-and-the-vm-variable/
And if you want to toggle the background on click event you should use this code. Cause the one you did is only adding the background, but not removing from the others li. To do this we need to modify the html and the controller a little bit:
<div ng-app="classApp" ng-controller="classCtrl">
<ul id = "bookmarkList">
<li ng-repeat="bookmark in markers" ng-class="{'active': selectedMarker === bookmark}" ng-click="jumpTo(bookmark)">{{bookmark.text}}</li>
</ul>
</div>
$scope.selectedMarker = null;
function highlightBookmark(marker) {
$scope.selectedMarker = marker;
}
$timeout(checkTime, 2000);
function checkTime(){
highlightBookmark($scope.markers[0])
}
$scope.jumpTo = function (marker) {
highlightBookmark(marker);
}
Cya.
var classApp = angular.module('classApp', []);
classApp.controller('classCtrl', function ($scope, $timeout) {
$scope.isActive = false;
$scope.activeButton = function () {
$scope.isActive = !$scope.isActive;
}
function checkTime() {
$scope.isActive = !$scope.isActive;
}
$timeout(checkTime, 2000)
});
Related
i have tried using angularjs and jquery for developing my websites and i'm not new to them. but somehow i stumbled to an issue which is not so familiar for me.
the main problem is between angular routing and jquery document ready script execution.
the scenario is i use angularjs ui-router to browse pages for my website(SPA) one of my page has inline jquery codes looks like this
<div id="details-page">
</div>
<script>
var counter = 0;
function foo() {
var setFoo = setInterval(function() {
counter++;
console.log(counter);
}, 2000);
}
</script>
foo();
the codes runs as expected,
but when i go route to another pages i can see my logs still running(the foo function) which seems weird for me but that's not the main problem, the main problem is when i go back to "details-page" the function foo is executed back(because of $(document).ready(function(){})) so two foo's is running in my system which is definitely destroyed everything.
so my goal here is how to STOP the old foo function or
how to STOP the new function foo from executing in short i just want one foo function running in my system, thanks
If I understand you correctly you want this counter to start when the user enters the "details-page", you want it to keep running when they leave, and when they come back it must use the original counter.
To solve this with AngularJS you should create a service, as your service will be a singleton and not linked specifically to the "details-page".
Fiddle example: https://jsfiddle.net/UncleDave/g9co2172/1/
angular
.module('app', [])
.controller('DetailsCtrl', function(counterService) {
counterService.start();
})
.service('counterService', function($interval) {
var counter = 0;
var started = false;
function start() {
if (started)
return;
started = true;
$interval(function() {
counter++;
console.log(counter);
}, 2000);
}
return {
start: start
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="DetailsCtrl as details">
</div>
just add this code in your controller,and don't forget t inject $rootScope and $interval services in your controller.
$rootScope.counter = 0;
var setFoo;
function foo() {
setFoo = $interval(function () {
$rootScope.counter++;
console.log($rootScope.counter);
}, 2000);
}
foo();
$rootScope.$on("$routeChangeStart", function () {
$interval.cancel(setFoo);
})
I've got the following code in a controller :
var deregisterSetPermissions = $rootScope.$on("setPermissions", function () {
[... some code used when the user click on a "create account" button...]
});
$scope.$on('$destroy', function() {
deregisterSetPermissions;
[... and other listeners are unregistered ...]
}
When I leave the page, all the listeners are unregistered : I can see through the chrome developer console that $rootScope.$$listeners.setPermissions[] contains a single value, which is null.
When I come back to the page and after it loaded, $rootScope.$$listeners.setPermissions[0] contains null and $rootScope.$$listeners.setPermissions[1] contains the function the listener will have to call.
$rootScope.$$listenerCount.setPermissions is 1.
But when I click on the "create account" button, the function used by the "setPermissions" listener is called twice !
How can it be ?
Here's what I tried :
1- Checked that there are no other listeners created with the name "setPermissions"
2- Delete the content of the listener when the scope is destroyed :
$scope.$on('$destroy', function() {
$rootScope.$$listeners.setPermissions=[];
$rootScope.$$listenerCount.setPermissions = 0;
});
But still after that, if I leave the page and come back 3 times, a click on the "create account" button will cause the "setPermissions"-listener-function to be called 3 times...
It would be nice to understand why the listener's function is called more than once; and if you have a solution or an idea to help me going on, I am looking forward to reading it !
deregisterSetPermissions is a function. To unsubscribe the listener, you have to call it.
$scope.$on('$destroy', function() {
deregisterSetPermissions();
}
Generally, any time you find yourself using a function object that begins with the double dollar signs ($$) in Angular, it means you're doing something wrong - that's Angular's naming convention for something that isn't part of their public API!
I found some useful information from this question:
how to unsubscribe to a broadcast event
Which I used to construct an example:
var app = angular.module('app', [])
.controller('AppController', function($rootScope) {
var appCtrl = this;
$rootScope.manageNumbers = false;
appCtrl.addNumber = function() {
$rootScope.$broadcast('addNumber');
};
})
.controller('NumbersController', function($rootScope, $scope) {
var numCtrl = this;
numCtrl.numbers = [];
numCtrl.number = 0;
// Register and set handle
console.log('Register listener');
var registerListener = $rootScope.$on('addNumber', addNumber);
logListenerData();
// Unregister
$scope.$on('$destroy', function() {
console.log('Unregister listener');
registerListener();
// $rootScope.$$listeners.addNumber.length = 0;
logListenerData();
});
function addNumber() {
numCtrl.numbers.push(numCtrl.number);
numCtrl.number++;
}
function logListenerData() {
console.log('$rootScope.$$listeners');
console.log($rootScope.$$listeners);
console.log('$rootScope.$$listenerCount');
console.log($rootScope.$$listenerCount);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="AppController as appCtrl">
<button ng-click="$root.manageNumbers=true;" ng-if="!$root.manageNumbers">
Start Number Management
</button>
<button ng-click="$root.manageNumbers=false;" ng-if="$root.manageNumbers">
Stop Number Management
</button>
<button ng-click="appCtrl.addNumber()">
Add Number
</button>
</div>
<div ng-controller="NumbersController as numCtrl" ng-if="$root.manageNumbers">
<p>
<b>Numbers:</b>
<span ng-repeat="number in numCtrl.numbers">
{{ $last ? number : number + ', ' }}
</span>
</p>
</div>
</body>
Notice a few things. Even when you unregister an event, unless that event was triggered while it was registered, it will have a "ghost" null value in the $$listeners but will not show up in the $$listenerCounts. With the commented out line that sets the length to 0 I was able to remove these ghosts, however; it causes no actual difference as far as I can tell.
I finally found the root of the problem.
There was an other rootScope-listener in a service which wasn't destroyed, which is emitting the "setPermissions" message. Thus, I could try all I wanted, but I was looking at the wrong place..
When I trigger deleteQuestion() a second time 2 questions get deleted. Any idea? Let me know if you need to see more of my code.
controller.js
crtPromoCtrl.controller('surveyCtrl', ['$scope', 'surveySrv', function($scope, surveySrv)
{
$scope.questions = surveySrv.getQuestions();
$scope.editQuestion = function(index)
{
surveySrv.setEditQuestion(index);
};
$scope.deleteQuestion = function(index)
{
$(document).off('click', '#confirmationModal #confirm');
$('#confirmationModal').modal('show');
$(document).on('click', '#confirmationModal #confirm', function()
{
surveySrv.deleteQuestion(index);
$scope.$apply();
});
};
}]);
service.js
crtPromoSrv.service('surveySrv', function()
{
var questions = [];
var editQuestion;
this.getQuestions = function()
{
return questions;
};
this.addQuestion = function(question)
{
questions.push(question);
};
this.setEditQuestion = function(index)
{
editQuestion = questions[index];
};
this.getEditQuestion = function()
{
return editQuestion;
};
this.clearEditQuestion = function()
{
editQuestion = undefined;
};
this.deleteQuestion = function(index)
{
questions.splice(index, 1);
console.log(questions);
};
});
EDIT: I'm thinking it's an event propagation thing, since when I have 5 q's it deletes #2 and #3 when I delete #2.
EDIT: Fixed, see controller.js code.
It appears you are adding the 'click' function to your #confirmationModal #confirm button multiple times. The first time $scope.deleteQuestion is called, it adds the function. The second time you call it, it adds it again so when it is clicked, the function is called twice.
A simple fix would be to unbind the 'click' event before adding it again. Something like this: $('#confirmationModal #confirm').off('click');
The better solution here is to not use jQuery at all for these event bindings. Using a simple Angular modal directive (like the one provided in the Angular-UI library, for instance) would be the correct way to do this. Then you can just have an ng-click on the button and never have this problem.
In a ticket entry page, I have a main ticketEntry.html page, which contains one grid for the lines and one for the payments.
When ticketEntry.html is loaded, it must first retrieve the ticket view model (via ajax calls to Web API). The line and payment grid cannot retrieve their data until the ticket view model has been received.
In my current solution, I have to use $timeout in the controller for ticketEntry.html for this to work. I am looking for a cleaner way.
Extracts from ticketEntry.html:
<div ng-controller="ticketLineController">
<div id="ticketLineGridDiv" kendo-grid="ticketLineGrid" k-options="ticketLineGridOptions"></div>
</div>
...
<div ng-controller="ticketPaymentController">
<div id="ticketPaymentGridDiv" kendo-grid="ticketPaymentGrid" k-options="ticketPaymentGridOptions"></div>
</div>
In the controller for ticketEntry.html, I have this:
$timeout(function () {
ticketService.getTicket(ticketId).then(
function(ticket) {
$scope.initPos(ticket);
},
...);
}, 500);
$scope.initPos = function(ticket) {
$scope.ticket = ticket; <-- $scope.ticket is used by the line and payment grid
$scope.$broadcast('PosReady'); <-- Tell the payment and line controllers to load their grids
}
As you can see, I am using $timeout to delay for 500ms, then I get the ticket view model and broadcast to the line and payment controller that they now can load their grids.
Here is the listener in the line controller:
$scope.$on('PosReady', function (event) {
$scope.ticketLineGrid.setDataSource(getGridDataSource());
$scope.ticketLineGrid.dataSource.read();
});
The problem is that if I do not use $timeout in the ticket entry controller, $scope.ticketLineGrid is sometimes undefined here (same thing with the payments controller).
I have tried using angular.element(document).ready(function () {...} instead of $timeout in the ticket entry controller, but that did not handle the issue.
How do I know when $scope.ticketLineGrid (for example) has been created/defined?
What is the proper way of handling this kind of scenario?
Update 9/27/2014, to provide more data on how the ticket line grid gets initialized:
In the AngularJs directive in ticketEntry.html, the k-options specifies the definition object for the grid:
<div id="ticketLineGridDiv" kendo-grid="ticketLineGrid" k-options="ticketLineGridOptions"></div>
ticketPaymentGridOptions is just an object with properties that defines the grid:
$scope.ticketPaymentGridOptions = {
autoBind: false,
height: 143,
columns: [
{
field: "payCode", title: "PayCode",
},
{
field: "amount", title: "Amount", format: "{0:n2}", attributes: { style: "text-align:right" },
},
],
pageable: false,
...
};
Update 9/29/2014: This is the solution I went with, based on suggestion by Valentin
I use two watches - one in the child scope where the ticketLineGrid lives:
$scope.$watch('ticketLineGrid', function (newVal) {
if (angular.isDefined(newVal)) {
$scope.ticketControl.lineGridReady = true;
}
});
This watch sets the parent property $scope.ticketControl.lineGridReady = true once the grid has been initialized.
The parent (ticketEntryController) has watches for lineGridReady:
$scope.$watch('ticketControl.lineGridReady', function (gridReady) {
if (gridReady) {
$scope.loadPage();
}
});
$scope.loadPage = function () {
ticketService.getTicket(ticketId).then(
function (ticket) {
$scope.initPos(ticket);
}
...
}
Not as clean as I would have liked it, but certainly better than using $timeout...
How do I know when $scope.ticketLineGrid (for example) has been created/defined?
You could use a $scope.$watch statement :
$scope.$watch('ticketLineGrid', function (newVal, oldVal) {
if(angular.isDefined(newVal)){
// do something with it
}
})
However, in my view the good way to do this is to retrieve the data not from a scope property, but from a promise. I would use only promises and no events at all for this :
var ticketPromise = ticketService.getTicket(ticketId);
ticketPromise.then(function (ticket) {
$scope.ticket = ticket;
});
// you know that part better than I do
var ticketLineGridPromise = ...;
$q.all([ticketPromise, ticketLineGridPromise])
.then(function (realizations) {
var ticket = realizations[0], ticketLineGrid = realizations[1];
$scope.ticketLineGrid.setDataSource(getGridDataSource());
$scope.ticketLineGrid.dataSource.read();
})
I can't be more precise because it's not clear from your code what initializes ticketLineGrid.
Finally, in many cases it's very handy to use resolve clauses in your route declaration.
I am investigating Angular as a potential framework to use for an upcoming project. The test app I am making consists of an unordered list of which list items can be added to via an add link. Each list item contains a number of checkboxes. The number of checkboxes for a particular list item can be increased or decreased with plus and minus links next to each list item. Observe:
Hopefully that makes sense. Each checkbox has an ng-model directive binding the value of the checkbox to a property in an object. When the application is in the state above clicking any of the checkboxes fires six checks (one for each checkbox) -- the entire $scope of the root controller is checked for changes. Ideally, only the $scope of the relevant list item would be checked for changes. How can I accomplish this? I've attached my test code for reference. I've tried adding ng-click="$event.stopPropagation()" to the input node as well as to the li node but this appears to increase (double) the number of checks in the digest.
HTML:
<div ng-app ng-controller="App">
<ul>
<li ng-repeat="line in lines" ng-controller="LineController">
<input type="checkbox" ng-repeat="box in line.boxes" ng-model="box.on" />
<a ng-show="line.boxes.length > 1" ng-click="removeBox()">-</a>
<a ng-click="addBox()">+</a>
</li>
</ul>
<a ng-click="addLine()">Add</a>
</div>
JavaScript:
function App($scope) {
$scope.lines = [];
$scope.addLine = function () {
$scope.lines.push({
boxes: []
});
};
}
function LineController($scope) {
$scope.addBox = function () {
var box = {};
Object.defineProperty(box, 'on', {
enmerable: true,
get: function () {
console.log('Get!');
return this._on;
},
set: function (on) {
this._on = on;
}
});
$scope.line.boxes.push(box);
};
$scope.removeBox = function () {
$scope.line.boxes.pop();
};
}
If your concern is that AnguarJS dirty checking is going to be too slow for your needs, your question really need to be "is AngularJS going to be to slow to build X?" If X is a 3D game with lots of constant rendering then the answer is probably yes, AngularJS is not what you want. If X is "a scalable business/consumer oriented single page application", then the dirty checking algorithm is not going to be your bottle neck.
This SO answer has a good explanation of how data binding works and talks a bit about performance concerns.
What about to use $watch. We can invoke watch only for specific row. That means if you have 4x4 matrix (4 rows , 4 columns) on any checkbox state change we call watch 4 times
var webApp = angular.module('myModule', []);
webApp.controller('App', function ($scope) {
$scope.lines = [];
$scope.addLine = function () {
console.log("addLine");
$scope.lines.push({
boxes: []
});
};
});
webApp.controller('LineController', function ($scope) {
$scope.addBox = function () {
var box = {};
/* Object.defineProperty(box, 'on', {
enmerable: true,
get: function () {
console.log('Get!');
return this._on;
},
set: function (on) {
this._on = on;
}
});*/
$scope.line.boxes.push(box);
$scope.$watch(function () {
return $scope.line.boxes;
},
function (newValue, oldValue) {
if(newValue == oldValue) return;
console.log('Get new checkbox!');
}, true);
};
$scope.removeBox = function () {
$scope.line.boxes.pop();
};
});
Demo Fiddle