I'm building a simple Pomodoro timer, and still in the early stages of learning Angular. I'm having trouble updating the view every second with the $scope.timeView variable. $scope.timeView logs to the console every second, but not the view. I have tried injecting $interval and using apply(), but their not working. I'm sure its something obvious to a trained eye, I'll keep searching. In the meantime any help would be greatly appreciated. Thanks.
pomodoro_timer.controller('app.controller', ['$scope', '$state', '$stateParams', function ($scope, $state, $stateParams) {
// (function() {
var intrvl;
var t = 1500;
var tDiv = $('#time');
$scope.startTimer = function() {
if (tDiv.hasClass("notWorking")) {
$scope.interval(t);
$scope.toggleClass();
}
};
$scope.interval = function() {
intrvl = setInterval(function(){
t -= 1;
$scope.displayTime(t)
},1000)
}
$scope.displayTime = function() {
var m = parseInt(t / 60);
var s = parseInt(t) % 60;
if (s < 10) {
s = "0" + s;
}
$scope.timeView = m+":"+s;
}
$scope.stopStart = function() {
if (tDiv.hasClass('working')) {
$scope.toggleClass();
clearInterval(intrvl);
$('#QwkBreak a').text('Continue Working'); //////////////////////Should I remove jQuery?/////////////////////////
} else if (t<1500) { // prevents timer from starting when '#QwkBreak' is clicked, unless timer has started counting down //
$('#QwkBreak a').text('Quick Break');
$scope.interval();
$scope.toggleClass();
}
}
$scope.toggleClass = function() {
tDiv.toggleClass('notWorking working');
}
$scope.resetTimer = function() {
if (!tDiv.hasClass('notWorking')) { //prevents reset button from toggling classes unless (class="working") //
clearInterval(intrvl);
t = 1500;
tDiv.text("25:00");
$scope.toggleClass();
}
}
// })();s
}]);
<body ng-controller="app.controller">
<h1>Pomodoro Timer</h1>
<div id="timeView">
<p id="time" class="notWorking">{{ timeView }}</p>
</div>
<div id="controls">
<button id="startWork"><a ng-click="startTimer()" href="#">Start Work</a></button>
<button id="QwkBreak"><a ng-click="stopStart()" href="#">Quick Break</a></button>
<button id="reset"><a ng-click="resetTimer()" href="#">Reset</a></button>
<button id="5_MInBreak">5-Min Break</button>
</div>
</div>
Angular has $interval service that internally will manage digests and also allows for easily getting rid of timers using cancel() method
$scope.interval = function() {
intrvl = $interval(function(){
t -= 1;
$scope.displayTime(t)
},1000)
}
Now you also want to remove that interval timer from the window when scope is destroyed
$scope.$on('$destroy', function(){
intrvl.cancel();
});
you will need to inject $interval in controller also
Also suggest that all of this timer related code should be placed in a directive. Controllers should not have any dom related code in them
Reference: $interval docs
Related
I have a page in which which has multiple tabs. I want to add the feature that the tab is reloaded automatically after a fixed duration. I have the following:
<uib-tab index="$index + 1" ng-repeat="environmentRegionTab in ctrl.environmentRegionTabs"
heading="{{environmentRegionTab.environmentRegionName}}"
select="ctrl.onEnvironmentRegionSelect(environmentRegionTab.id);">
<div class="panel-body tab-content">
<div class="alert alert-success" ng-show="ctrl.deployStatus[ctrl.environmentRegion.name].show">
<strong>Launched deployment with execution id
{{ctrl.deployStatus[ctrl.environmentRegion.name].id}}</strong>
</div>
...................
And the following controller:
export function ServiceDetailController(ecsServiceResponse, teamListResponse, productListResponse, businessSubOwnerListResponse, serviceInstancesResponse, businessOwnerListResponse, EcsService, SecretsService, $location, $stateParams, $uibModal, $scope, $state, $window) {
'ngInject';
var self = this;
var serviceInstanceId;
self.ecsAuthControl = {};
self.initialize = _initialize;
self.clearMessages = _clearMessages();
self.onEnvironmentRegionSelect = _onEnvironmentRegionSelect;
$scope.reloadRoute = function() {
$state.reload();
};
function _onEnvironmentRegionSelect(serviceInstanceId) {
self.selectedserviceInstanceId = serviceInstanceId;
if (serviceInstanceId) {
$location.search('serviceInstanceId', serviceInstanceId);
_loadEnvironmentRegion();
} else {
$location.search('serviceInstanceId', null);
_loadSummary();
}
}
}
I am not able to understand how to add the fixed time duration? I also would like to show a counter ticking down from 5 to 0 after which the page is reloaded. How can I do it? I declared the reload function but I am not able to figure out how to add a fixed timer? Thanks!
Make use of $interval service in angularjs:
$interval(function () {
$scope.reloadRoute();
}, 5000);
(make sure to pass $interval as a dependency to controller)
Example Plunker
Here is one of the safest way through which you can achieve the functionality.
Function which does the refresh:-
var poll = function() {
console.log("polling");
$scope.doRefresh(); // Your refresh logic
};
Call the poll from StartPollar:
var startPoller = function() {
if (angular.isDefined(stop)) {
stopPoller();
}
stop = $interval(poll, $scope.intervalTime); //$scope.intervalTime : refresh interval time
};
If you want to Stop it:
var stopPoller = function() {
if (angular.isDefined(stop)) {
$interval.cancel(stop);
stop = undefined;
console.log("cancelled poller operation");
} else {
console.log("do nothing");
}
};
I am using angular modal service to show incoming call popup.
Everything seems to work but in particular case the popup closes leaving behind grey overlay blocking the whole UI.
Popup closes perfectly when i manually click reject and close button provided in popup but gives unusual behaviour when i use timeout to close the popup whithout doing any operation on it.
For reference i am giving my whole code.
----------------------------modal popup UI code---------------------------
<div class="modal fade">
<div class="modal-dialog modal-lg modal-dialog-custom">
<div class="modal-content modal-content-dialog">
<div class="modal-header">
<audio class="incoming-videoconference-audio" autoplay loop>
<source src="../images/dataCallIncoming.mp3" type="audio/mpeg">
</audio>
<button type="button" class="close" ng-click="vm.hangUp()" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Incoming Call</h4>
</div>
<img class="incoming-nowConf-logo" src="../images/new_nowconfer_e.png" />
<div id="state" class="grid_4 alpha">
<div class="gps_ring"></div>
</div>
<div class="modal-body modal-body-custom">
<div style="text-overflow:ellipsis;overflow:hidden;" class="call-from">
{{vm.confName}}
</div>
<div class="call-control">
<button type="button"class="btn-sm btn-sm-gray cancel-btn" ng-click="vm.hangUp()" data-dismiss="modal">Reject</button>
<span style="width:50px;"> </span>
<button type="button"class="btn-sm btn-sm-green" ng-click="vm.accept()" data-dismiss="modal">Answer</button>
</div>
</div>
</div>
</div>
</div>
-------------------------modal popup controller------------------------------
(function () {
'use strict';
angular
.module('incomingModule')
.controller('IncomingCallController', IncomingCallController);
IncomingCallController.$inject = ['$scope','$rootScope','plivoclient','$routeParams','$location','close','from', 'instId','confName','$timeout'];
function IncomingCallController($scope,$rootScope , plivoclient,$routeParams ,$location,close, from, instId,confName,$timeout) {
var vm = this;
vm.connecting = false;
vm.from = from;
vm.confName = confName;
vm.dismissModal = function(result) {
plivoclient.conn.reject();
console.log('vm.dismissModal::'+result);
close(result, 200); // close, but give 200ms for bootstrap to animate
};
activate();
function activate(){
$timeout(function(){
vm.dismissModal('cancel');
},25000);
}
vm.accept = function() {
plivoclient.conn.answer();
vm.connecting = true;
console.log("incoming call accept............");
vm.dismissModal('accept');
$timeout(function(){
$location.path( "/call/"+$rootScope.id2);
},300);
};
vm.hangUp = function() {
plivoclient.conn.reject();
vm.dismissModal('reject');
console.log("incoming call hangedup............");
};
}
}());
-------------------------opening modal code----------------------------------------
ModalService.showModal({
templateUrl: '../../partials/calls.incoming.popup.html',
controller: 'IncomingCallController',
controllerAs: 'vm',
inputs: {
from: dataNew.callerName || '',
instId: dataNew.extraHeaders['X-Ph-Instid'] || dataNew.extraHeaders['X-Ph-instid'],
confName:$rootScope.conferenceData.conf_name
}
}).then(function(modal) {
modal.element.modal();
modal.close.then(function(result) {
//$scope.message = result ? "You said Yes" : "You said No";
});
});
----------------------------------angular modal service code----------------------------------
'use strict';
let module = angular.module('angularModalService', []);
module.factory('ModalService', ['$animate', '$document', '$compile', '$controller', '$http', '$rootScope', '$q', '$templateRequest', '$timeout',
function($animate, $document, $compile, $controller, $http, $rootScope, $q, $templateRequest, $timeout) {
function ModalService() {
var self = this;
// Returns a promise which gets the template, either
// from the template parameter or via a request to the
// template url parameter.
var getTemplate = function(template, templateUrl) {
var deferred = $q.defer();
if (template) {
deferred.resolve(template);
} else if (templateUrl) {
$templateRequest(templateUrl, true)
.then(function(template) {
deferred.resolve(template);
}, function(error) {
deferred.reject(error);
});
} else {
deferred.reject("No template or templateUrl has been specified.");
}
return deferred.promise;
};
// Adds an element to the DOM as the last child of its container
// like append, but uses $animate to handle animations. Returns a
// promise that is resolved once all animation is complete.
var appendChild = function(parent, child) {
var children = parent.children();
if (children.length > 0) {
return $animate.enter(child, parent, children[children.length - 1]);
}
return $animate.enter(child, parent);
};
self.showModal = function(options) {
// Get the body of the document, we'll add the modal to this.
var body = angular.element($document[0].body);
// Create a deferred we'll resolve when the modal is ready.
var deferred = $q.defer();
// Validate the input parameters.
var controllerName = options.controller;
if (!controllerName) {
deferred.reject("No controller has been specified.");
return deferred.promise;
}
// Get the actual html of the template.
getTemplate(options.template, options.templateUrl)
.then(function(template) {
// Create a new scope for the modal.
var modalScope = (options.scope || $rootScope).$new();
var rootScopeOnClose = $rootScope.$on('$locationChangeSuccess', cleanUpClose);
// Create the inputs object to the controller - this will include
// the scope, as well as all inputs provided.
// We will also create a deferred that is resolved with a provided
// close function. The controller can then call 'close(result)'.
// The controller can also provide a delay for closing - this is
// helpful if there are closing animations which must finish first.
var closeDeferred = $q.defer();
var closedDeferred = $q.defer();
var inputs = {
$scope: modalScope,
close: function(result, delay) {
if (delay === undefined || delay === null) delay = 0;
$timeout(function() {
cleanUpClose(result);
}, delay);
}
};
// If we have provided any inputs, pass them to the controller.
if (options.inputs) angular.extend(inputs, options.inputs);
// Compile then link the template element, building the actual element.
// Set the $element on the inputs so that it can be injected if required.
var linkFn = $compile(template);
var modalElement = linkFn(modalScope);
inputs.$element = modalElement;
// Create the controller, explicitly specifying the scope to use.
var controllerObjBefore = modalScope[options.controllerAs];
var modalController = $controller(options.controller, inputs, false, options.controllerAs);
if (options.controllerAs && controllerObjBefore) {
angular.extend(modalController, controllerObjBefore);
}
// Finally, append the modal to the dom.
if (options.appendElement) {
// append to custom append element
appendChild(options.appendElement, modalElement);
} else {
// append to body when no custom append element is specified
appendChild(body, modalElement);
}
// We now have a modal object...
var modal = {
controller: modalController,
scope: modalScope,
element: modalElement,
close: closeDeferred.promise,
closed: closedDeferred.promise
};
// ...which is passed to the caller via the promise.
deferred.resolve(modal);
function cleanUpClose(result) {
// Resolve the 'close' promise.
closeDeferred.resolve(result);
// Let angular remove the element and wait for animations to finish.
$animate.leave(modalElement)
.then(function () {
// Resolve the 'closed' promise.
closedDeferred.resolve(result);
// We can now clean up the scope
modalScope.$destroy();
// Unless we null out all of these objects we seem to suffer
// from memory leaks, if anyone can explain why then I'd
// be very interested to know.
inputs.close = null;
deferred = null;
closeDeferred = null;
modal = null;
inputs = null;
modalElement = null;
modalScope = null;
});
// remove event watcher
rootScopeOnClose && rootScopeOnClose();
}
})
.then(null, function(error) { // 'catch' doesn't work in IE8.
deferred.reject(error);
});
return deferred.promise;
};
}
return new ModalService();
}]);
I have spent hours on internet to figure out why this is happening but failed to solve it,i feel when any click event happens then it works fine but fails to close properly when on operation is performed.Please help!!
thanks in advance
I had the same issue and it was due to a comment at the top of my HTML file. When I removed the comment, it worked fine.
I didn't get the reason of this bug though.
hope you have the same case.
Basically I have a timeline with posts that is a $firebaseArray and any change to this array is getting binded properly. But when I want to bind any other data it only binds when ngInfiniteScroll is trying to retrieve more data from firebase, so only when I scroll down.
In the code bellow I'm calling {{getMoreDetails()}} and this data is binded when the first set of data is being retrieved with ngInfiniteScroll but as soon as it is loaded the bind breaks and only binds again when scrolling.
My concerns here are:
Was ngInfiniteScroll designed to work this way?
Is there any workaround in this scenario?
Stack:
"firebase": "2.4.2","angularfire": "~1.2.0","firebase-util": "0.2.5","ngInfiniteScroll": "1.2.2"
timeline.html
<div ng-controller="TimelineController">
<section class="entrys main-content" infinite-scroll="posts.scroll.next(3)" infinite-scroll-distance="0.3">
<div class="inner">
<div ng-repeat="post in filteredPostsResults = (posts | filter:postIdFilter)">
<article class="entry">
<img ng-if="post.sourceType=='IMAGE'" data-ng-src="{{getPostData(post)}}"/>
<div class="entry-info">
<h3><div ng-bind-html="post.description | emoticons"></div></h3>
<small>posted on <time>{{getDateInFormat(post.createdAt)}}</time></small>
{{getMoreDetails()}}
</div>
</article>
</div>
</div>
</section>
</div>
timeline.js
(function (angular) {
"use strict";
var timeline = angular.module('myApp.user.timeline', ['firebase', 'firebase.utils', 'firebase.auth', 'ngRoute', 'myApp.user.timelineService']);
timeline.controller('TimelineController', [ '$scope', '$routeParams', 'TimelineService', '$publisherServices', '$securityProperties', function ($scope, $routeParams, TimelineService, $publisherServices, $securityProperties) {
if (!$scope.posts){
$scope.posts = TimelineService.getPosts($routeParams.userId);
}
$scope.posts.$loaded(function(result) {
$scope.isPostsLoaded = true;
});
$scope.getMoreDetails = function() {
console.log("LOGGED ONLY WHEN SCROLLING");
return $publisherServices.getDetails();
};
$scope.getPostData = function(post) {
if (!post.dataUrl){
post.dataUrl = $publisherServices.getAwsFileUrl(post.fileName);
}
return post.dataUrl;
};
$scope.postIdFilter = function(post) {
if ($routeParams.postId){
if (post.$id == $routeParams.postId) return post;
} else { return post; }
};
$scope.getDateInFormat = function(timestamp){
var date = new Date();
date.setTime(timestamp);
return date;
};
}]);
})(angular);
timelineService.js
(function (angular) {
"use strict";
var timelineService = angular.module('myApp.user.timelineService', []);
timelineService.service('TimelineService', ['$routeParams', 'FBURL', '$firebaseArray', function ($routeParams, FBURL, $firebaseArray) {
var posts;
var currentUserIdPosts;
var postsRef;
var self = {
getPosts: function(userId){
if (!posts || userId != currentUserIdPosts){
currentUserIdPosts = userId;
postsRef = new Firebase(FBURL).child("posts").child(userId);
var scrollRef = new Firebase.util.Scroll(postsRef, "createdAtDesc");
posts = $firebaseArray(scrollRef);
posts.scroll = scrollRef.scroll;
}
return posts;
}
}
return self;
}]);
})(angular);
I am assuming that you want the post details updated when the data from your Firebase changes.
When Firebase changes are applied to your scope, it seems that it doesn't trigger a digest cycle, so you probably need to do it manually every time you get updates from Firebase.
Take a look at $$updated in $firebaseArray.$extend (see docs).
// now let's create a synchronized array factory that uses our Widget
app.factory("WidgetFactory", function($firebaseArray, Widget) {
return $firebaseArray.$extend({
// override the update behavior to call Widget.update()
$$updated: function(snap) {
// we need to return true/false here or $watch listeners will not get triggered
// luckily, our Widget.prototype.update() method already returns a boolean if
// anything has changed
return this.$getRecord(snap.key()).update(snap);
}
});
});
I hope this helps.
From what I've read, it seems using $rootScope.$broadcast is not advisable unless absolutely necessary. I'm using it in a service to notify a controller that a variable has changed. Is this incorrect? Is there a better way to do it? Should I be using watch instead (even though the variable only changes on user interaction) ?
the service:
function Buildservice($rootScope) {
var vm = this;
vm.box= [];
var service = {
addItem: addItem,
};
return service;
// Add item to the box
// Called from a directive controller
function addItem(item) {
vm.box.push(item);
broadcastUpdate();
}
function broadcastUpdate() {
$rootScope.$broadcast('updateMe');
}
// In the controller to be notified:
// Listener for box updates
$scope.$on('updateMe', function() {
// update variable binded to this controller
});
// and from a separate directive controller:
function directiveController($scope, buildservice) {
function addToBox(item){
buildservice.addItem(item);
}
So this works just fine for me, but I can't figure out if this is the way I should be doing it. Appreciate the help!
If you are in same module, why don't you use $scope instead of $rootScope?
You can use a callback function to notify the controller something has changed. You supply the service a function from the controller, and invoke that particular function whenever your variable has been changed. You could also notify multiple controllers if needed.
I have created a small example:
HMTL:
<div ng-controller="CtrlA as A">
{{A.label}}
<input type="text" ng-model="A.input" />
<button ng-click="A.set()">set</button>
</div>
<div ng-controller="CtrlB as B">
{{B.label}}
<input type="text" ng-model="B.input" />
<button ng-click="B.set()">set</button>
</div>
JS
var app = angular.module('plunker', []);
app.controller('CtrlA', function(AService) {
var vm = this;
vm.label = AService.get();
vm.notify = function() {
vm.label = AService.get();
}
vm.set = function() {
AService.set(vm.input)
}
AService.register(vm.notify);
});
app.controller('CtrlB', function(AService) {
var vm = this;
vm.label = AService.get();
vm.notify = function() {
vm.label = AService.get();
}
vm.set = function() {
AService.set(vm.input)
}
AService.register(vm.notify);
});
app.factory("AService", function() {
var myVar = "Observer";
var observers = [];
return {
get: function() {
return myVar;
},
set: function(name) {
console.log(name);
myVar = name;
this.notify();
},
register: function(fn) {
observers.push(fn);
},
notify: function() {
for( i = 0; i < observers.length; i++) {
observers[i]();
}
}
}
})
You will see upon executing this that the controllers get notified when the internal variable has been changed. (Notice: I haven't filtered the original sender from the list) (Plnkr)
Alert function is firing off twice, I can't figure out why: http://jsfiddle.net/ntzLrkmz/2/
It's going to alert when a !number is inserted at <input type="number">
EDIT: thanks all, I'll be playing with this useful information
This happens because angular evals the list when the times variable change and $diggest to build the ng-reapeat with the new value. You should use $watch instead:
angular.module('helloApp', [])
.controller('helloController', ['$scope', function ($scope) {
$scope.times = 1;
$scope.$watch('times', function (newValue, oldVaue) {
if (!angular.isNumber(newValue)) {
$scope.times = oldVaue;
alert('ENTER A VALID POSITIVE NUMBER PLEASE');
}
});
$scope.getTimes = function (n) {
if (angular.isNumber(n)) {
return new Array(n);
}
};
}]);
Working example here: http://jsfiddle.net/fals/75uv4ugb/
By restructuring your logic and a slight change to your ng-repeat you can solve this issue:
DEMO EXAMPLE
HTML:
<p ng-repeat="obj in array track by $index" ng-show="name.length">Welcome {{name}} !</p>
JS:
angular.module('helloApp', [])
.controller('helloController', ['$scope', function ($scope) {
$scope.array = [];
$scope.populateArray = function (n, t) {
$scope.array = [];
if (t > 0) {
for (i = 0; i < t; i++) {
$scope.array.push(n);
}
}
}
}]);