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
Related
I'm with a problem with binding an object of a Factory and a Controller and it's view.
I am trying to get the fileUri of a picture selected by the user. So far so good. The problem is that I am saving the value that file to overlays.dataUrl. But I am referencing it on the view and it isn't updated. (I checked and the value is actually saved to the overlays.dataUrl variable.
Here goes the source code of settings.service.js:
(function () {
"use strict";
angular
.module("cameraApp.core")
.factory("settingsService", settingsService);
settingsService.$inject = ["$rootScope", "$cordovaFileTransfer", "$cordovaCamera"];
function settingsService($rootScope, $cordovaFileTransfer, $cordovaCamera) {
var overlays = {
dataUrl: "",
options: {
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
destinationType: Camera.DestinationType.FILE_URI
}
};
var errorMessages = [];
var service = {
overlays: overlays,
selectOverlayFile: selectOverlayFile,
errorMessages: errorMessages
};
return service;
function selectOverlayFile() {
$cordovaCamera.getPicture(overlays.options).then(successOverlay, errorOverlay);
}
//Callback functions
function successOverlay(imageUrl) {
//If user has successfully selected a file
var extension = "jpg";
var filename = getCurrentDateFileName();
$cordovaFileTransfer.download(imageUrl, cordova.file.dataDirectory + filename + '.' + extension, {}, true)
.then(function (fileEntry) {
overlays.dataUrl = fileEntry.nativeURL;
}, function (e) {
errorMessages.push(e);
});
}
function errorOverlay(message) {
//If user couldn't select a file
errorMessages.push(message);
//$rootScope.$apply();
}
}
})();
Now the controller:
(function () {
angular
.module("cameraApp.settings")
.controller("SettingsController", SettingsController);
SettingsController.$inject = ["settingsService"];
function SettingsController(settingsService) {
var vm = this;
vm.settings = settingsService;
activate();
//////////////////
function activate(){
// Nothing here yet
}
}
})();
Finnally on the view:
<h1>{{vm.settings.overlays.dataUrl}}</h1>
<button id="overlay" class="button"
ng-click="vm.settings.selectOverlayFile()">
Browse...
</button>
Whenever I change the value in the factory, it doesn't change in the view.
Thanks in advance!
Unfortunately Factories in angularjs are not meant to be used as two way bindings. Factories and Services are only singletons. They are only there to be used when called.
Ex Factory:
app.factory('itemFactory', ['$http', '$rootScope', function($http, $rootScope) {
var service = {};
service.item = null;
service.getItem = function(id) {
$http.get(baseUrl + "getitem/" + id)
.then(function successCallback(resp) {
service.item = resp.data.Data;
$rootScope.$broadcast("itemready");
}, function errorCallback(resp) {
console.log(resp)
});
};
return service;
}]);
I use the $broadcast so if I call getItem my controller knows to go get the fresh data.
Ex Directive:
angular.module("itemApp").directive("item", ['itemFactory', '$routeParams', '$location', '$rootScope', '$timeout', function (itemFactory, $routeParams, $location, $rootScope, $timeout) {
return {
restrict: 'E',
templateUrl: "components/item.html",
link: function (scope, elem, attr) {
scope.item = itemFactory.item;
scope.changeMade = function(){
itemFactory.getItem(1);
}
scope.$on("itemready", function () {
scope.item = itemFactory.item;
})
}
}
}]);
So as you can see in my code above anytime I need a fresh item I use $broadcast and $on to update my service and directive. I hope this makes sense, feel free to ask any questions.
As pointed by Ohjay44, the factory is not updated on the view. The way to do it is using a directive (also as Ohjay44 said). To use $broadcast, $emit and $on and keep the encapsulation I did what is recommended by John Papa's Angular Style Guide: created a factory (in my case a named it comms).
Here goes the newly created directive (overlay.directive.js):
(function () {
angular
.module('cameraApp.settings')
.directive('ptrptSettingsOverlaysInfo', settingsOverlaysInfo);
settingsOverlaysInfo.$inject = ["settingsService", "comms"];
function settingsOverlaysInfo(settingsService, comms) {
var directive = {
restrict: "EA",
templateUrl: "js/app/settings/overlays.directive.html",
link: linkFunc,
controller: "SettingsController",
controllerAs: "vm",
bindToController: true // because the scope is isolated
};
return directive;
function linkFunc(scope, element, attrs, vm) {
vm.overlays = settingsService.overlays;
comms.on("overlaysUpdate", function (event, overlays) {
vm.overlays = overlays;
});
}
}
})();
I created overlay.directive.html with:
<div class="item item-thumbnail-left">
<img ng-src="{{vm.overlays.dataUrl}}">
<h2>{{vm.overlays.dataUrl}}</h2>
</div>
And finally I put an $emit on the settingsService where the overlay is updated:
(function () {
"use strict";
angular
.module("cameraApp.core")
.factory("settingsService", settingsService);
settingsService.$inject = ["comms", "$cordovaFileTransfer", "$cordovaCamera"];
function settingsService(comms, $cordovaFileTransfer, $cordovaCamera) {
var overlays = {
dataUrl: "",
options: {
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
destinationType: Camera.DestinationType.FILE_URI
}
};
var errorMessages = [];
var service = {
overlays: overlays,
selectOverlayFile: selectOverlayFile,
errorMessages: errorMessages
};
return service;
function selectOverlayFile() {
$cordovaCamera.getPicture(overlays.options).then(successOverlay, errorOverlay);
}
//Callback functions
function successOverlay(imageUrl) {
//If user has successfully selected a file
var extension = "jpg";
var filename = getCurrentDateFileName();
$cordovaFileTransfer.download(imageUrl, cordova.file.dataDirectory + filename + '.' + extension, {}, true)
.then(function (fileEntry) {
overlays.dataUrl = fileEntry.nativeURL;
// New code!!!!
comms.emit("overlaysUpdated", overlays);
}, function (e) {
errorMessages.push(e);
});
}
function errorOverlay(message) {
//If user couldn't select a file
errorMessages.push(message);
//$rootScope.$apply();
}
}
})();
I used an $emit instead of a broadcast to prevent the bubbling as explained here: What's the correct way to communicate between controllers in AngularJS?
Hope this helps someone else too.
Cheers!
I wanted to display the added users dynamically in the dashboard.
My code is in the following way.
Controller: where the actual action triggers .
Adding the user function
$scope.addUser= function(){
modalService.addUser();
}
function init(){
// Someother functions
getUserRequests()
};
function getUserRequests() {
datacontext.getExtranetUserRequests()
.then(function (data){
vm.ExtranetUserRequest = data;
});
};
Service: modalService
addUser: function (column) {
var modalInstance = $modal.open({
templateUrl: 'app/NewExtranetSite/Popup/userModal.html',
controller: 'userModal',
});
updateUser: function(){
// updates the user
});
Controller :userModal
In the userModal.html after adding the info and on clicking save, add user function will be triggered.
function addUser(){
datacontext.saveNewExtranetuserRequest($scope.user);
};
I would like to initiate the getUserRequests() after the completion of add user in the user modal
So that the newly added user can be visble on the dashboard without refreshing the page
Let me answer u shortly.
You have a view where you are adding user details from input using one form ().
On ng-submit or ng-click action you can call one method in your that particular view's controller.
Now to display user details, you might know json. So create a blank $scope variable which will contain added user details.($scope.variable=[];)
Now on submit just hit **
$sope.variable.push({'key':value,'value':value});
**
once your object is populated with new data it will automatically displayed in the view.
5. We have just awesome ng-repeat angular's directive to show dynamical list containing objects.
6. **
ng-repeat="key in variable track by $index"
**
The $modal.open function returns a promise, so it's easy to wait for the modal to close and then execute another function. Let 'addUser' return this promise, then wait for it to finish before executing getUserRequests:
in modalService:
addUser: function (column) {
var modalInstance = $modal.open({
templateUrl: 'app/NewExtranetSite/Popup/userModal.html',
controller: 'userModal',
});
return modalInstance;
}
in controller:
$scope.addUser= function(){
modalService.addUser().then((resultReturnedFromModal) => {
getUserRequests();
});
}
Sorry for the bad post.
Let me explain briefly in this post
I would like to display the added data dynamically on the page.
I have a controller where the user addition action takes place.
(function () {
'use strict';
var controllerId = 'newUser';
angular.module('app').controller(controllerId,
['modalService', '$scope', 'dataContext', newUser]);
function newUser(modalService, $scope, dataContext) {
init();
function init() {
var extranetSiteRequestId = +$routeParams.id;
if (extranetSiteRequestId && extranetSiteRequestId > 0) {
getItem(extranetSiteRequestId);
getUserRequests();
}
}
$scope.newuserRequest = function () {
modalService.addUser();
}
function getUserRequests() {
datacontext.getExtranetUserRequests().then(function (data) {
vm.UserData = data;
});
};
}
}());
I am using a service modalService to handle the add user request.
(function (){
'use strict';
var serviceId = 'modalService'
angular.module('app').service(serviceId, ['$modal', modalService]);
function modalService($modal) {
return {
addUser: function () {
var modalInstance = $modal.open({
templateUrl: 'app/NewExtranetSite/Popup/userModal.html',
controller: 'userModal',
});
modalInstance.result.then(function (userDetails) {
if (userDetails) {
alert(userDetails) ;
};
})
},
};
}
})();
finally in the userModal controller am handling the new user added request
(function () {
'use strict';
var controllerId = 'userModal';
angular.module('app').controller(controllerId, ['$scope', '$modalInstance', 'datacontext', 'common', addUserModalFunction]);
function addUserModalFunction($scope, $modalInstance, datacontext, common) {
var vm = $scope;
vm.cancel = cancel;
vm.submit = addUser;
init();
function init() {
common.logger.log("controller loaded", null, controllerId);
common.activateController([], controllerId);
}
function cancel() {
$modalInstance.close();
}
$scope.open = function ($event, opened) {
$event.preventDefault();
$event.stopPropagation();
$scope[opened] = true;
};
function addUser() {
datacontext.saveNewExtranetuserRequest($scope.user).then(function(data){
$modalInstance.close($scope.user);
});
};
};
})();
Now the problem is I would like to add a success or then function in the newUser Controller after the modalService.adduser complete
EX: modalService.addUser().then(function(results){
});
Fikkatra Thanks for the reply but couldn't able to achieve
am very bad # angular
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
I'm trying to write an AngularJS library for Pusher (http://pusher.com) and have run into some problems with my understanding of the digest loop and how it works. I am writing what is essentially an Angular wrapper on top of the Pusher javascript library.
The problem I'm facing is that when a Pusher event is triggered and my app is subscribed to it, it receives the message but doesn't update the scope where the subscription was setup.
I have the following code at the moment:
angular.module('pusher-angular', [])
.provider('PusherService', function () {
var apiKey = '';
var initOptions = {};
this.setOptions = function (options) {
initOptions = options || initOptions;
return this;
};
this.setToken = function (token) {
apiKey = token || apiKey;
return this;
};
this.$get = ['$window',
function ($window) {
var pusher = new $window.Pusher(apiKey, initOptions);
return pusher;
}];
})
.factory('Pusher', ['$rootScope', '$q', 'PusherService', 'PusherEventsService',
function ($rootScope, $q, PusherService, PusherEventsService) {
var client = PusherService;
return {
subscribe: function (channelName) {
return client.subscribe(channelName);
}
}
}
]);
.controller('ItemListController', ['$scope', 'Pusher', function($scope, Pusher) {
$scope.items = [];
var channel = Pusher.subscribe('items')
channel.bind('new', function(item) {
console.log(item);
$scope.items.push(item);
})
}]);
and in another file that sets the app up:
angular.module('myApp', [
'pusher-angular'
]).
config(['PusherServiceProvider',
function(PusherServiceProvider) {
PusherServiceProvider
.setToken('API KEY')
.setOptions({});
}
]);
I've removed some of the code to make it more concise.
In the ItemListController the $scope.items variable doesn't update when a message is received from Pusher.
My question is how can I make it such that when a message is received from Pusher that it then triggers a digest such that the scope updates and the changes are reflected in the DOM?
Edit: I know that I can just wrap the subscribe callback in a $scope.$apply(), but I don't want to have to do that for every callback. Is there a way that I can integrate it with the service?
On the controller level:
Angular doesn't know about the channel.bind event, so you have to kick off the cycle yourself.
All you have to do is call $scope.$digest() after the $scope.items gets updated.
.controller('ItemListController', ['$scope', 'Pusher', function($scope, Pusher) {
$scope.items = [];
var channel = Pusher.subscribe('items')
channel.bind('new', function(item) {
console.log(item);
$scope.items.push(item);
$scope.$digest(); // <-- this should be all you need
})
Pusher Decorator Alternative:
.provider('PusherService', function () {
var apiKey = '';
var initOptions = {};
this.setOptions = function (options) {
initOptions = options || initOptions;
return this;
};
this.setToken = function (token) {
apiKey = token || apiKey;
return this;
};
this.$get = ['$window','$rootScope',
function ($window, $rootScope) {
var pusher = new $window.Pusher(apiKey, initOptions),
oldTrigger = pusher.trigger; // <-- save off the old pusher.trigger
pusher.trigger = function decoratedTrigger() {
// here we redefine the pusher.trigger to:
// 1. run the old trigger and save off the result
var result = oldTrigger.apply(pusher, arguments);
// 2. kick off the $digest cycle
$rootScope.$digest();
// 3. return the result from the the original pusher.trigger
return result;
};
return pusher;
}];
I found that I can do something like this and it works:
bind: function (eventName, callback) {
client.bind(eventName, function () {
callback.call(this, arguments[0]);
$rootScope.$apply();
});
},
channelBind: function (channelName, eventName, callback) {
var channel = client.channel(channelName);
channel.bind(eventName, function() {
callback.call(this, arguments[0]);
$rootScope.$apply();
})
},
I'm not really happy with this though, and it feels as though there must be something bigger than I'm missing that would make this better.
$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;});