I'm trying to make a little notifier, that informs about typical situations: need authorization, changes saved etc. Notices are shown for 3 seconds and disappear, if user didn't click on it (if notice clicked, it disappears immediatly).
Documentation is not very informative.
How should i use $timeout, to call close(); after 3 seconds?
And how can i put a variable (nId) into function? I tried with closure (*function(){return function(){}}*) in default setTimeOut(), but unsuccessfully.
myApp.controller('noticesCtrl',
function noticesCtrl($scope, $rootScope, noticesData){
$rootScope.notices = [];
$scope.closeNotice = function(nId){
noticesData.close(nId);
};
});
myApp.factory('noticesData', function($rootScope, $timeout){
return{
add: function(type, text){
var nId = $rootScope.notices.length + 1;
$rootScope.notices.push({id: nId, type:type, text:text+nId});
// call close function with 3sec. delay; how?
},
close: function(nId){
angular.forEach($rootScope.notices, function(notice, key){
if(notice.id == nId){
$rootScope.notices.splice(key,1);
}
});
}
}
});
myApp.factory('noticesData', function($rootScope, $timeout){
var obj = {};
obj.add = function(type, text){
var nId = $rootScope.notices.length + 1;
$rootScope.notices.push({id: nId, type:type, text:text+nId});
$timeout(function(){
obj.close(nId);
},3000);
}
obj.close = function(nId){
angular.forEach($rootScope.notices, function(notice, key){
if(notice.id == nId){
$rootScope.notices.splice(key,1);
}
});
}
return obj;
});
Heres how to do it
$timeout(function () {
// do something
}, 50);
Related
Is there a way to add a timeout to an AngularJS $watch function?
For example, let's say I have the below AngularJS code that's watching a value, myName. When the value changes, the listener function runs. But if the value does not change within a certain period of time, I want it to do something else.
Specifically, in the code below, I would want $scope.nothingEnteredFlag to change from false to true. My html template be set up to reflect the state of that flag (e.g., using ng-show).
var app = angular.module("helloApp", []);
app.controller("helloCtrl", function($scope) {
$scope.nothingEnteredFlag=false;
$scope.$watch("myName", function (newValue, oldValue) {
if ($scope.myName.length < 5) {
$scope.message = "Short name!";
} else {
$scope.message = "Long name!";
}
});
});
See fiddle.
I've tried surrounding the $watch with $timeout, but can't seem to get that to work.
You can use angular timeout to achieve your desire result.
var timer;
var timerFunction = function() {
timer = $timeout(function() {
$scope.nothingEnteredFlag = true;
}, 5000);
};
This is will create the timer function
Your controller should like this
var app = angular.module("helloApp", []);
app.controller("helloCtrl", function($scope, $timeout) {
$scope.nothingEnteredFlag = false;
$scope.myName = "";
$scope.$watch("myName", function(newValue, oldValue) {
if ($scope.myName.length < 5) {
$scope.message = "Short name!";
} else {
$scope.message = "Long name!";
}
$scope.nothingEnteredFlag = false;
$timeout.cancel(timer);
timerFunction();
});
var timer;
var timerFunction = function() {
timer = $timeout(function() {
$scope.nothingEnteredFlag = true;
}, 5000);
};
timerFunction();
});
As you can see we have enabled timeout of the 5 seconds once user enters any text we cancel the timer and enable it again, this way we can prompt the user to enter if he hasn't wrote anything in five seconds.
Demo
I want to hide my headmenu.
app.controller("kpiOverviewCtrl", function ($scope, $stateParams,) {
"use strict";
var setUpController = function () {
$scope.headmenu = $state.current.controller === "kpiCompareCtrl";
};
$rootScope.$on('$locationChangeSuccess', function () {
setUpController();
});
$rootScope.$on('$stateChangeSuccess', function () {
setUpController();
});
setUpController();
});
As you can see on the code it sets headmenu to true on a controller switch. It works fine. But now I want to set headmenu to true on a ng-click statment from a controller thats already been loaded.
app.controller("kpiDetailsCtrl", function ($scope, $state) {
"use strict";
$scope.loadDataForMonthView = function () {
$scope.errorNoDataForDate = false;
$scope.yearMode = false;
$scope.monthMode = true;
//Here I want to set $scope.headmenu = true;
//Other code.....
};
Any nice suggestions?
Use a broadcast. They're a great way for communication between controllers.
Create a regular function in your main controller, which you can call from within the controller itself.
app.controller('Main', function($scope) {
function setHeadMenu() {
// Set menu to true
}
$scope.$on('setHeadMenu', function() {
setHeadmenu(); // Fires on broadcast
})
});
Create an ng-click which fires a broadcast from the other controller
app.controller('Second', function($scope) {
$scope.click = function() {
$scope.$broadcast('setHeadMenu'); // Send a broadcast to the first controller
}
});
You can declare new method to $rootScope inside kpiOverviewCtrl:
app.controller("kpiOverviewCtrl", function ($scope, $stateParams, $rootScope) {
"use strict";
//your code...........
$rootScope.setUpController = setUpController;
});
And then call it from kpiDetailsCtrl controller:
app.controller("kpiDetailsCtrl", function ($scope, $state, $rootScope) {
"use strict";
$scope.loadDataForMonthView = function () {
$scope.errorNoDataForDate = false;
$scope.yearMode = false;
$scope.monthMode = true;
$rootScope.setUpController();
}
});
First dummy suggestion:
$scope.loadDataForMonthView = function () {
$scope.headmenu = true; //(or false)
}
But most likely you are using some asynchrounous call, so something like this would be better:
$scope.loadDataForMonthView = function () {
// First: you need some promise object
// the most simple is to use $http
var promise = $http({url: 'some.url', method: 'GET'});
promise.then(function() {
// the data have arrived to client
// you can hide or show menu according to your needs
$scope.headmenu = true; //(or false)
})
}
More on how $http works is in the docs https://docs.angularjs.org/api/ng/service/$http
i have 2 controllers who are not in the same scope or have a parent child relation.
So i want to call from controlleB a function in ControllerA. In my case its a listContoller with an addItem function and i want to call this function from a addItemController somewhere else on the page after clicking submit. i know this should work with a service, but i dont know how.
app.controller("listCtrl", ["$scope", "listSvc", function ($scope, listSvc){
$scope.list.data = listSvc.load("category");
$scope.addItem = function(newitem) {
$scope.list.data.unshift(newitem);
...
}
}]);
app.controller("addItemCrtl", ["$scope", "listSvc", function ($scope, listSvc){
$scope.addItem = function() {
listSvc.addItem($scope.newItem);
}
}]);
app.service('listSvc', function() {
return{
load: function(section){
...
},
addItem: function(item){
addItem(item); <<-- call function in listController
}
}
});
UPDATE
k is this better? i put the list.data inside my service and i watch from my controller if the list change and put it on the scope from my controller that ng-repeat can do his work... is this appraoch better? or have someone better tips for me how i should do this...
app.service('listSvc', ['$http', function($http) {
var list = {};
return {
list:{
get: function () {
return list.data;
},
set: function (data) {
list.data = data;
}
},
addItem: function(item){
var response = $http.post("/api/album/"+$scope.list.section, item);
response.success(function(){
list.data.unshift(item);
console.log("yeah success added item");
}).error(function(){
console.log("buuuh something went wrong");
});
return response;
},
load: function(section){
var response = $http.get("/api/album/"+section);
response.success(function(data){
list.set(data);
list.section = section;
console.log("yeah success loaded list");
}).error(function(){
console.log("buuuh something went wrong");
});
return response;
}
};
}]);
and in my controllers i do this
app.controller("listCrtl", ["$scope", "listSvc", function ($scope, listSvc){
listSvc.load("category");
...
$scope.$watch('listSvc.list.get()', function(data) {
$scope.list.data = data;
});
...
}]);
app.controller("addItemCrtl", ["$scope", "listSvc", function ($scope, listSvc){
...
$scope.addItem = function() {
listSvc.addItem($scope.newItem);
}
...
}]);
gregor ;)
I just solved this myself! Perhaps this may help:
The function inside of my Controller:
var timeoutMsg = function() {
vm.$parent.notification = false;
};
The function inside my Service (I had to pass in $timeout as well as the name of the function from my Controller, now it works):
// original broken code:
// this.modalSend = function(vm) {
// fixed:
this.modalSend = function(vm, $timeout, timeoutMsg) {
vm.$parent.sendTransaction = function() {
// Show notification
vm.$parent.message = 'Transaction sent!';
vm.$parent.notification = true;
$timeout(timeoutMsg, 4000);
// original broken code:
// $timeout(timeoutMsg(), 4000);
};
}
var vm = $scope
For example, let's assume I need to run a function after receiving two events "eventA" and "eventB". What I usually do is to declare for each event a boolean variable, set the variable to true when the event is received, and ask if the other variable is true to run the function:
var a = false,
b = false;
$scope.$on("eventA", function(){
a = true;
if (b)
performTask();
});
$scope.$on("eventB", function(){
b = true;
if (a)
performTask();
});
var performTask = function() {
/* do something... */
};
This gets more complex if there are three or more events. Is there a design pattern to handle these cases?
You can use $q promises.
var dfdATask= $q.defer();
var dfdBTask= $q.defer();
$scope.$on("eventA", function(){
// whatever this function does
dfdATask.resolve(true);//or pass a value
});
$scope.$on("eventB", function(){
//whatever this function does
dfdBTask.resolve(true);//or pass a value
});
$q.all([dfdATask.promise, dfdBTask.promise]).then(function(){
//be sure to pass in an array of promises
//perform task
})
So theory wise if you only want to execute this magical action after you've received these two events have been called at least once then you probably want to use promises.
app.controller('ExampleOneController', [
'$log',
'$scope',
'$q',
'$rootScope',
function ($log, $scope, $q, $rootScope) {
$scope.anotherAction1FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-1-a', function () {
$log.log('Fired e-1-a');
aDeferred.resolve();
});
$scope.$on('e-1-b', function () {
$log.log('Fired e-1-b');
bDeferred.resolve();
});
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 1!');
$scope.anotherAction1 = 'Hello World 1!';
$scope.anotherAction1FiredCount++;
});
}
]);
That being said usually I want to execute everytime two things happen so I tend to 'reset' my promises.
app.controller('ExampleTwoController', [
'$log',
'$scope',
'$q',
function ($log, $scope, $q) {
$scope.anotherAction2FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-2-a', function () {
$log.log('Fired e-2-a');
aDeferred.resolve();
});
$scope.$on('e-2-b', function () {
$log.log('Fired e-2-b');
bDeferred.resolve();
});
var wait = function () {
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 2!');
$scope.anotherAction2 = 'Hello World 2!';
$scope.anotherAction2FiredCount++;
aDeferred = $q.defer();
bDeferred = $q.defer();
wait();
});
};
wait();
}
]);
Here's the working plunker!
Promises are life.
active polling using $scope.$watch:
One way to do this:
var a = false, b = false;
$scope.$on("eventA", function(){ a = true; });
$scope.$on("eventB", function(){ b = true; });
$scope.$watch(
function() { return a && b; },
function(newval, oldval) {
if (newval) { performTask(); }
}
);
one step further:
var events = { a: false, b: false };
$scope.$on("eventA", function(){ events.a = true; });
$scope.$on("eventB", function(){ events.b = true; });
$scope.$watch(
function() {
var result = true;
for (var key in events) {
result = result && events[key];
}
return result;
},
function(newval, oldval) {
if (newval) { performTask(); }
}
);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
Be sure to read the developer guide and check the "Scope $watch Performance Considerations" section.
regular callback:
var events = { a: false, b: false };
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
performTask();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with one promise, $q.defer():
var events = { a: false, b: false };
var shouldPerform = $q.defer();
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
shouldPerform.resolve();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
shouldPerform.promise.then(performTask);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with multiple promises...
Already been covered by multiple answers.
Promises are meant for your use case. But since you mentioned you were looking for a design pattern, I'll put down one way to do this using the Observer pattern.
You can check out this live Plunkr: http://plnkr.co/edit/1Oqn2TAGTr7NLYZd9ax1?p=preview
Has an angularjs service that handles the logic for tracking events and calling the final action.
The controller simply defines your events, the final event and registers them with your service.
app.controller('MainCtrl', function($scope, EventService) {
var events = [];
... //define events
EventService.registerEvents(events);
EventService.registerEventsCallback(finalEvent); //the observer
});
The service makes this work by removing a called event from the events list upon first execution.
app.factory('EventService', function(){
var events = [];
var finalEvent;
var eventsCallback = function(){
if(!events.length){
finalEvent();
}
}
var resolveEvent= function(event){
var eventIndex = events.indexOf(event);
if(eventIndex>=0){
events.splice(eventIndex,1);
}
}
return{
registerEvents: function(eventsList){
events = angular.copy(eventsList);
},
registerEventsCallback: function(event){
finalEvent = event;
},
publishEvent: function(event){
event();
resolveEvent(event);
eventsCallback();
}
}
});
$watch() is not catching return sseHandler.result.cpuResult.timestamp after the first iteration. I'm not sure why, because I verified the datestamps are changing. Also, after the first iteration....if I click on the view repeatedly, the scope variables and view update with the new information...so it's like $watch does work...but only if I click on the view manually to make it work.
'use strict';
angular.module('monitorApp')
.controller('homeCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.controller('cpuCtrl', ['$scope', 'sseHandler', function($scope, sseHandler) {
$scope.sseHandler = sseHandler;
$scope.avaiable = "";
$scope.apiTimeStamp = sseHandler.result.cpuResult.timestamp;
$scope.infoReceived = "";
$scope.last15 = "";
$scope.last5 = "";
$scope.lastMinute = "";
var cpuUpdate = function (result) {
$scope.available = result.cpuResult.avaiable;
$scope.apiTimeStamp = result.cpuResult.timestamp;
$scope.infoReceived = new Date();
$scope.last15 = result.cpuResult.metrics['15m'].data
$scope.last5 = result.cpuResult.metrics['5m'].data
$scope.lastMinute = result.cpuResult.metrics['1m'].data
}
$scope.$watch(function () {
console.log("being caught");
return sseHandler.result.cpuResult.timestamp},
function(){
console.log("sss");
cpuUpdate(sseHandler.result);
});
}]);
angular.module('monitorApp')
.controller('filesystemsCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.controller('httpPortCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.factory('sseHandler', function ($timeout) {
var source = new EventSource('/subscribe');
var sseHandler = {};
sseHandler.result = { "cpuResult" : { timestamp : '1'} };
source.addEventListener('message', function(e) {
result = JSON.parse(e.data);
event = Object.keys(result)[0];
switch(event) {
case "cpuResult":
sseHandler.result = result;
console.log(sseHandler.result.cpuResult.timestamp);
break;
}
});
return sseHandler;
});
The changes in sseHandler.result.cpuResult.timestamp happen otuside of the Angular context (in the asynchronously executed event-listener callback), so Angular does not know about the changes.
You need to manually trigger a $digest loop, by calling $rootScope.$apply():
.factory('sseHandler', function ($rootScope, $timeout) {
...
source.addEventListener('message', function (e) {
$rootScope.$apply(function () {
// Put all code in here, so Angular can also handle exceptions
// as if they happened inside the Angular context.
...
});
}
...
The reason your random clicking around the app made it work, is because you probably triggered some other action (e.g. changed a model, triggered and ngClick event etc) which in turn triggered a $digest cycle.
Your message event in the EventListener does not start a new digest cycle. In your sseHandler try:
$timeout(function () {sseHandler.result = result;});