Managing Multiple Counters in Angularjs - javascript

Within my application I need to use of multiple counters on the same page. Each of these counters will count from 0 to 100%, back to 0, and again to 100%.
I am using interval to accomplish this using the below simplified block of code
$interval(function() {
if (data[counter] < 100) {
data[counter] = data[counter] + interval;
} else {
data[counter] = 0;
}
}, 1000);
The requirements I am attempting to solve for are:
The amount of counters on the page may vary depending on results from a DB
Based on events, any particular counter may be started or stopped
The counters must be independently defined to facilitate unique counting intervals
I feel the best approach would be to create an independent block of code that could be executed when I expect counting to begin and a block where I can execute a stop command.
My first attempt at this was to create a service within Angular. It worked great for the first counter and solved for the last 2 requirements, however because of Angular treating services as singletons it did not allow for multiple independent counters on a page.
My questions is looking for direction on the best way to approach this. I've seen recommendations of creating services as APIs, but I also see the potential of using directives. Does anyone have recommendations?

Here is an answer based on directives. I split up the actual counter from the GUI. So, you could use any GUI you like.
The counter and GUI together look like this:
<counter count="counter1Count" api="counter1Api"></counter>
<counter-gui count="{{counter1Count}}" api="counter1Api"></counter-gui>
Notice how using the same variable links them together. Check out the code snippet for the full example:
var app = angular.module('myApp',[]);
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.counter1Api={};
}]);
app.directive('counterGui',function(){
return {
restrict: 'E',
template : '<h1>{{count}}</h1>' +
'<button ng-click="api.start()" class="btn btn-default" type="button">Start</button>' +
'<button ng-click="api.stop()" class="btn btn-default" type="button">Stop</button>' +
'<button ng-click="api.reset()" class="btn btn-default" type="button">Reset</button>',
scope: {
count : "#",
api : "="
}
};
});
app.directive('counter',function(){
return {
restrict: 'E',
controller: ['$scope','$interval', function myCounterController($scope,$interval) {
var intervalPromise= null;
reset();
function reset() {
$scope.count= 0;
console.log("reset",$scope.count);
}
function start() {
// Make sure the timer isn't already running.
if (!intervalPromise){
intervalPromise= $interval(function() {
if ($scope.count < 100) {
$scope.count++;
} else {
$scope.count = 0;
}
}, 1000);
}
}
function stop() {
if (intervalPromise) {
$interval.cancel(intervalPromise);
intervalPromise = null;
}
}
$scope.api={
reset : reset,
start : start,
stop : stop
};
}],
scope: {
count : "=",
api : "="
}
};
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AngularJS Counter Example</title>
<!-- AngularJS -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<h1>Counter 1</h1>
<counter count="counter1Count" api="counter1Api"></counter>
<counter-gui count="{{counter1Count}}" api="counter1Api"></counter-gui>
<h1>Counter 2</h1>
<counter count="counter2Count" api="counter2Api"></counter>
<counter-gui count="{{counter2Count}}" api="counter2Api"></counter-gui>
<h1>Counter 3</h1>
<p>Two GUIs displaying the same counter.</p>
<counter count="counter3Count" api="counter3Api"></counter>
<counter-gui count="{{counter3Count}}" api="counter3Api"></counter-gui>
<counter-gui count="{{counter3Count}}" api="counter3Api"></counter-gui>
</div>
</body>
</html>

Related

Update background image without refresh angularjs

Hey everyone I am currently building a mobile app for my company and I have a angularjs function that is currently adding a css class based on whether it's day or night. It does what it's supposed to do perfectly by putting a day or night image based on whether it's day or night.
The issue I am running into is that when testing the app right before the clock let's say goes from 1:59 to 2 it doesn't change the background image until you refresh the page. I want it to automatically change the background image without having to refresh and can't seem to find a way to do it. I will link the code here!
Any help is appreciated as it has me completely stumped...
Here is what I have in my html.
<div class="dashboard-welcome" ng-class="{'hide-homescreen-image-landscape'
: isLandscape, 'homescreenImageDay' : isDay, 'homescreenImageNight' :
isNight }">
Here is where the function is being called
angular.module('starter').controller('homeCtrl', ['$scope', '$interval',
'jsBridge', 'authService', 'tierService', '$window', fnc]);
function fnc($scope, $interval, jsBridge, authService, tierService, $window)
{
$scope.$on('$ionicView.afterEnter', function (event, viewData) {
setOrientation();
$scope.isDay = !isDayTime(1000);
$scope.isNight = isDayTime(1000);
});
Here is where the function is instantiated. Basically checking what time it is.
var isDayTime = function () {
var h = new Date().getHours();
if (h >= 14) {
return true;
} else {
return false;
}
}
I can't supply all the code since this application is thousands of lines long but this is the working function as of now. Just need it to switch background images without refreshing using angularjs...
Assuming that inside your div you are using an background-image: url("...")
I suggest you set up an object to hold a single $scope.isDay value instead of doing the calculation twice.
Also use the $interval service to check every nth millisecond to update your $scope.isDay value.
The code below works fine in dynamically changing a background image on a page using its CSS class.
HTML:
<div ng-class="data.isDay ? 'back1' : 'back2'"></div>
JS:
var exampleApp = angular.module('exampleApp', []);
exampleApp.controller('exampleController', function($scope, $interval) {
$scope.data = {
isDay: true,
classVal: {
one: 'text-danger',
two: 'text-success'
}
};
$interval(function() {
$scope.toggleImage();
}, 600);
$scope.toggleImage = function() {
$scope.data.isDay = ($scope.data.isDay ? false : true)
};
$scope.toggleImage();
});
Here is also a plnkr demo

How can this re-usable AngularJS service update on mousemove?

I developed a re-usable AngularJS service that allows the user to start, stop, and re-set a countdown timer from a view by clicking on buttons in the view. The re-usable service can be accessed through any controller that includes it. The working code for the minimal example countdown app can be viewed by clicking the link to this plnkr.
But I want the timer to be re-set to its max default value every time a user moves the mouse anywhere in the browser window. This means adding $window.addEventListener(...) somewhere in the service because the service has to be re-usable across any controller, while also responding to any movement of the mouse anywhere over the browser window, even the areas not contained within an HTML element linked to a controller. Thus, I cannot simply resort to adding ng-mousemove="somemethod()" in the html body tag the way this other example does. I would also prefer to avoid the $rootScope.broadcast approach taken by this other posting because I would like to keep the code isolated in the service as much as possible.
What specific changes need to be made to the code below so that the timer will be re-set to its default value any time the user moves the mouse anywhere in the browser window?
Though all of the code is in the plnkr for easy editing, I am also including it here.
index.html is:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Timer</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="myTimer.js" type="text/javascript"></script>
<script src="exampleController.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
</head>
<body ng-app="intervalExample">
<div>
<div ng-controller="ExampleController">
Test variable: {{ mytimer.testvariable }}<br>
Time Remaining : <font color='red'>{{mytimer.timeleft}}</font>
<br>
<button type="button" data-ng-click="mytimer.startCountdown()">Start Countdown</button>
<button type="button" data-ng-click="mytimer.stopCountdown()">Stop Countdown</button>
<button type="button" data-ng-click="mytimer.resetCountdown()">Reset Timer</button>
</div>
</div>
</body>
</html>
app.js is:
angular.('intervalExample',['ExampleController']);
exampleController.js is:
angular
.module('ExampleController', ['mytimer'])
.controller('ExampleController', ['$scope', 'mytimer', function($scope, mytimer) {
$scope.mytimer = mytimer;
}]);
myTimer.js is:
angular
.module('mytimer', [])
.service('mytimer', ['$rootScope', '$interval', function($rootScope, $interval) {
var $this = this;
this.testvariable = "some value. ";
this.timeleft = 15;
var stop;
this.startCountdown = function() {
// Don't start a new countdown if we are already counting down
if ( angular.isDefined(stop) ) return;
stop = $interval(function() {
if ($this.timeleft > 0 ) {
$this.timeleft = $this.timeleft - 1;
} else {
$this.stopCountdown();
}
}, 1000);
};
this.stopCountdown = function() {
if (angular.isDefined(stop)) {
$interval.cancel(stop);
stop = undefined;
}
};
this.resetCountdown = function() {
this.timeleft = 15;
};
// this.$on('$destroy', function() {
// Make sure that the interval is destroyed too
// $this.stopCountdown();
// });
function subsFunc() {
$window.addEventListener('mousemove', function(e) {
$this.timeleft = 15;
})
}
}]);
Issues to consider:
You are never calling subsFunc() and when you do will see that
$window is not injected in service
You will need to debounce the mousemove callback since the event
triggers about every pixel. Resetting your timer every pixel makes no
sense and would cause significant needless digests.
Use of directive for buttons would negate needing to inject in numerous controllers
Same for timer display ... can be directive and depending on UI combined with buttons

Applying angularjs ng-repeat to owl-carousel

<div class="owl-carousel">
<div ng-repeat="items in itemlist">
<img ng-src="{{items.imageUrl}}" />
</div>
<div>
<img src="http://placehold.it/350x150" />
</div>
</div>
View carousel here: Owl-carousel2
I'm running into an issue where whenever the ng-repeat directive is applied to carousel the items are stacked vertically instead of being layout horizontally.
If I leave out ng-repeat and use static items then it works as it should.
Is there a directive I can write and apply to owl-carousel to maintain the layout?
Also what is about ng-repeat that is causing the carousel to break?
Is angular somehow stripping the owl-carousel classes applied to the carousel?
Note* If build the list manually then iterate through and append the elements using :
var div = document.createElement('div');
var anchor = document.createElement('a');
var img = document.createElement('img');
.....
carousel.appendChild(div);
then call the owl.owlCarousel({..}) It works, not sure if this is the best work around because ng-repeat makes everything bit easier.
I discovered a hack , if I wrap the owl init in a timeout then ng-repat works.
setTimeout(function(){
...call owl init now
},1000);
<link rel="stylesheet" href="css/owl.carousel.css"/>
<link rel="stylesheet" href="css/owl.theme.default.min.css"/>
.....
<script src="/js/lib/owl.carousel.min.js"></script>
<script>
$(document).ready(function() {
var owl = $('.owl-carousel');
owl.owlCarousel({
.....
});
owl.on('mousewheel', '.owl-stage', function(e) {
if (e.deltaY > 0) {
owl.trigger('next.owl');
} else {
owl.trigger('prev.owl');
}
e.preventDefault();
});
})
</script>
Was able to modify a directive from DTing on another post to get it working with multiple carousels on the same page. Here is a working plnkr
-- Edit --
Have another plnkr to give an example on how to add an item. Doing a reinit() did not work cause any time the owl carousel is destroyed it loses the data- elements and can never initialize again.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.items1 = [1,2,3,4,5];
$scope.items2 = [1,2,3,4,5,6,7,8,9,10];
}).directive("owlCarousel", function() {
return {
restrict: 'E',
transclude: false,
link: function (scope) {
scope.initCarousel = function(element) {
// provide any default options you want
var defaultOptions = {
};
var customOptions = scope.$eval($(element).attr('data-options'));
// combine the two options objects
for(var key in customOptions) {
defaultOptions[key] = customOptions[key];
}
// init carousel
$(element).owlCarousel(defaultOptions);
};
}
};
})
.directive('owlCarouselItem', [function() {
return {
restrict: 'A',
transclude: false,
link: function(scope, element) {
// wait for the last item in the ng-repeat then call init
if(scope.$last) {
scope.initCarousel(element.parent());
}
}
};
}]);
Here is the HTML
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.carousel.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.theme.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.transitions.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.carousel.min.js" />
<script data-require="angular.js#1.3.x" src="https://code.angularjs.org/1.3.15/angular.js" data-semver="1.3.15"></script>
<script data-require="jquery#2.1.3" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.carousel.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<data-owl-carousel class="owl-carousel" data-options="{navigation: true, pagination: false, rewindNav : false}">
<div owl-carousel-item="" ng-repeat="item in ::items1" class="item">
<p>{{::item}}</p>
</div>
</data-owl-carousel>
<data-owl-carousel class="owl-carousel" data-options="{navigation: false, pagination: true, rewindNav : false}">
<div owl-carousel-item="" ng-repeat="item in ::items2" class="item">
<p>{{::item}}</p>
</div>
</data-owl-carousel>
</body>
</html>
I tried fiddling around with different directives but had no luck until I discovered this, it might a bit of a hack fix, but it works nonetheless.
Here is my div setup:
<div ng-repeat="item in mediaitems">
<img ng-src="item.imageurl" />
</div>
$scope.mediaitems is generated using ajax call. I found that if I delayed the owl initialization until my list was populated then it would render it properly. Also if you decide you want update you list dynamically just call the setupcarousel function (look below) after the list has been populated and it will re-init the carousel.
Note that carousel init is in an external file within an anonymous function. That's just how I choosed to set it up, you can have yours in-line or however you please.
In my controller I had something like this :
$scope.populate = function(){
$timeout(function(){
$scope.mediaitems = returnedlist; //list of items retrun from server
utils.setupcarousel(); //call owl initialization
});
};
var utils = (function(){
var setupcarousel = function(){
console.log('setting up carousel..');
var owl = $('.owl-carousel');
if(typeof owl.data('owlCarousel') != 'undefined'){
owl.data('owlCarousel').destroy();
owl.removeClass('owl-carousel');
}
owl.owlCarousel({
loop: false,
nav: true,
margin: 10,
responsive: {
0: {items: 3 },
600: {items: 5},
960: { items: 8},
1200:{items: 10},
1600:{items: 12}
}
});
};
return{
....
}
})();
The Angular UI Team has put together a set of bootstrap components implemented as angular directives. They are super sleek and fast to implement, and because they are directives, you don't run into issues with using jquery in an angular project. One of the directives is a carousel. You can find it here and here. I messed around with carousels for a long time with angular. I got the owl to work after some annoying tinkering, but AngularUI's implementation is much easier.
This is the same answer as mentioned by JKOlaf. However I've added responsive behaviour to it which provides better UX.
2 major improvements:
1. Fully responsive code resulting better UX in different devices.
2. The "autoHeight" property is handled now resulting smaller thumbnail of the images.
Code goes below:
Was able to modify a directive from DTing on another post to get it working with multiple carousels on the same page. Here is a working plnkr
-- Edit -- Have another plnkr to give an example on how to add an item. Doing a reinit() did not work cause any time the owl carousel is destroyed it loses the data- elements and can never initialize again.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.items1 = [1,2,3,4,5];
$scope.items2 = [1,2,3,4,5,6,7,8,9,10];
}).directive("owlCarousel", function() {
return {
restrict: 'E',
transclude: false,
link: function (scope) {
scope.initCarousel = function(element) {
// provide any default options you want
var defaultOptions = {
};
var customOptions = scope.$eval($(element).attr('data-options'));
// combine the two options objects
for(var key in customOptions) {
defaultOptions[key] = customOptions[key];
}
defaultOptions['responsive'] = {
0: {
items: 1
},
600: {
items: 3
},
1000: {
items: 6
}
};
// init carousel
$(element).owlCarousel(defaultOptions);
};
}
};
})
.directive('owlCarouselItem', [function() {
return {
restrict: 'A',
transclude: false,
link: function(scope, element) {
// wait for the last item in the ng-repeat then call init
if(scope.$last) {
scope.initCarousel(element.parent());
}
}
};
}]);
You can change the responsive item counts as per your requirement. Set it to a smaller value for larger thumbnail of images.
Hope this will help somebody and Thanks to the original answer provider.

Memory leak with angularJS and jQuery / angular bootstrap

I have a ng-repeat list that updates every minute. Its a list of cards that contains stuff like title, description, dates and so on.
In those cards there's also a angular-ui-bootstrap popover which i use to display comments on those cards.
When the list updates, the popover will keep some reference which creates a lot of detached dom elements.
Heres some of the code.
The directive i use.
.directive('mypopover', function ($compile, $templateCache) {
var getTemplate = function (contentType) {
var template = '';
switch (contentType) {
case 'user':
template = $templateCache.get("templateId.html");
break;
}
return template;
}
return {
restrict: "A",
link: function ($scope, element, attrs) {
var popOverContent;
popOverContent = getTemplate("user");
popOverContent = $compile("<span>" + popOverContent + "</span>")($scope);
var options = {
content: popOverContent,
placement: "bottom",
html: true,
trigger: "manual",
selector: '.fa-comment',
date: $scope.date,
animation: true
};
$(element).popover(options).on("mouseenter", function () {
var _this = this;
$(this).popover("show");
$('.popover').linkify();
$(".popover").on("mouseleave", function () {
$(this).popover('destroy');
$('.popover').remove();
});
}).on("mouseleave", function () {
var _this = this;
setTimeout(function () {
if (!$(".popover:hover").length) {
$(this).popover('destroy');
$('.popover').remove();
}
}, 350);
});
var destroy = function () {
$(element).popover('destroy');
}
$scope.$on("$destroy", function () {
destroy();
});
}
}
})
from the html..
The bo-something is the just a one way bind i use instead of the normal double bind from angular
<a bo-href="c.ShortUrl" target="_blank" bindonce ng-repeat="c in cards | filter:searchText | limitTo: limitValue[$index] track by c.Id">
<div class="panel detachable-card">
<div class="panel-body" bo-class="{redLabel: c.RedLabel, orangeLabel: c.OrangeLabel}">
<!-- Comments if any -->
<script type="text/ng-template" id="templateId.html">
<div ng-repeat="comment in c.Comment track by $index">
<strong style="margin-bottom: 20px; color:#bbbbbb; white-space: pre-wrap;">{{c.CommentMember[$index]}}</strong>
<br />
<span style="white-space: pre-wrap;">{{comment}}</span>
<hr />
</div>
</script>
<span bo-if="c.Comment" data-container="body" mypopover style="float:right"><i class="fa fa-comment fa-lg"></i></span>
<!-- Card info -->
<strong style="font-size:12px; color:#999999"><span bo-if="!c.BoardNameOverride" bo-text="c.NameBoard"></span> <span bo-if="c.BoardNameOverride" bo-text="c.BoardNameOverride"></span></strong>
<br />
<strong bo-text="c.Name"></strong>
<br />
<span bo-if="c.Desc" bo-text="c.Desc"><br /></span>
</div>
</div>
</a>
Heres a heap-snapshot of the site after one update.
http://i.stack.imgur.com/V4U1O.png
So Im fairly bad at javascript in general, and i have my doubts about the directive. I would have thought that the .popover('destroy') would remove the reference, but it doesnt seem to..
Any help is greatly appreciated..
Why are you constantly destroying the popup over and over? There is no need to destroy the popup every time the mouse is moved. Just show and hide the popup. It's much nicer on memory than constantly destroying and recreating the popup.
But, what you may not realize is that bootstrap components don't play well with AngularJS. Bootstrap components weren't architected in ways that allow the content within them to be updated easily which poses problems when you use them with AngularJS because the update model is built into the framework. And that's why the AngularUI project rewrote their Javascript components from the ground up in AngularJS so they behave as you would expect. I think you'll find those much easier to use.
http://angular-ui.github.io/bootstrap/
If you are using bootstrap 2.3 AngularUI v0.8 was the last version supporting bootstrap v2.3.

Multi State button like toggle in angularJS

I want to implement a feature in table where user can set value of cell by clicking on it.
there can be say 3-4 states,also a ng-model attached to it.
I looked for the toggle button in angularjs but they are mere on/off type.
In short; Clicking on the button will set the value as: Active, Inactive, Excluded
Looking for solution with multiple state.
Any help with this is really appreciated.
Check the below working example :
http://jsfiddle.net/vishalvasani/ZavXw/9/
and controller code
function MyCtrl($scope) {
$scope.btnarr=0;
$scope.btnTxt=["Active","Inactive","Excluded"]
$scope.change=function(){
switch($scope.btnarr)
{
case 0:
$scope.btnarr=1;
break;
case 1:
$scope.btnarr=2
break;
case 2:
$scope.btnarr=0;
break;
}
}
}
OR
Shorter Version of Controller
function MyCtrl($scope) {
$scope.btnarr=0;
$scope.btnTxt=["Active","Inactive","Excluded"]
$scope.change=function(){
$scope.btnarr = ($scope.btnarr + 1) % $scope.btnTxt.length;
}
}
and HTML
<div ng-controller="MyCtrl">
<button ng-modle="btnarr" ng-Click="change()">{{btnTxt[btnarr]}}</button>
</div>
There isn't much to it.
When I make menus in Angular, on each item, I'll have a "select" function, which then selects that particular object, out of the list...
Making an iterable button is even smoother:
var i = 0;
$scope.states[
{ text : "Active" },
{ text : "Inactive" },
{ text : "Excluded" }
];
$scope.currentState = $scope.states[i];
$scope.cycleState = function () {
i = (i + 1) % $scope.states.length;
$scope.currentState = $scope.states[i];
// notify services here, et cetera
}
<button ng-click="cycleState">{{currentState.text}}</button>
The actual array of states wouldn't even need to be a part of the $scope here, if this was the only place you were using those objects -- the only object you'd need to have on the $scope would then be currentState, which you set when you call the cycleState method.
Here is a fiddle with two possibilities: selecting the state from a list or cycling by clicking the button itself.
http://jsfiddle.net/evzKV/4/
The JS code looks like this:
angular.module('test').directive('toggleValues',function(){
return {
restrict: 'E',
replace: true,
template: '<div>Set Status:<div ng-repeat="value in values" class="status" ng-click="changeTo($index)">{{value}}</div><span ng-click="next()">Current Status (click to cycle): {{values[selectedValue]}}</span></div>',
controller: ['$scope', '$element', function ($scope, $element) {
$scope.values = ["Active", "Inactive", "Pending"];
$scope.changeTo = function (index) {
$scope.selectedValue = (index < $scope.values.length) ? index : 0;
};
$scope.next = function () {
$scope.selectedValue = ($scope.selectedValue + 1) % $scope.values.length;
// the modulo is stolen from Norguard (http://stackoverflow.com/a/18592722/2452446) - brilliant idea
};
$scope.selectedValue = 0;
}]
};
});
HTML:
<div ng-app="test">
<toggle-values></toggle-values>
</div>

Categories