AngularJS update Service values when connected or disconnected to firebase - angularjs

I wish to change the icon color when connected or disconnected to the firebase server. I got this far:
HTML
<button class="button button-icon ion-cloud" ng-style="dbConnectedStyle"></button>
Controller
firebaseRef.$loaded().then( function() {
$scope.dbConnectedStyle = {'color': dbConnectStatus.color};
}
Service
.service('dbConnectStatus', function(firebaseRef){
var status = false;
var color = 'transparent';
var connectedRef = firebaseRef.child(".info/connected");
connectedRef.on("value", function(snap) {
status = snap.val();
if (status) {
color = 'lightgrey';
console.log("Connected to DB (" + color + ")" );
} else {
color = 'transparent';
console.log("Disonnected to DB (" + color + ")" );
}
});
return {
'boolean': status,
'color': color
}
})
It change color the first time. But when disconnecting it doesn't change... seems like it's not two-way binding to the service. How do I achieve this?
UPDATE
Tried to do a reference to the Service as an object rather than doing primitives assignments as explained in the good tutorial A Tale of Frankenstein and Binding to Service Values in Angular.js
I changed the code to the following
HTML
<button class="button button-icon ion-cloud"
ng-style="dbConnectionStatus.connectionStyle">
</button>
Service
.service('dbConnectStatus', function(firebaseRef, $rootScope){
this.status = false;
var styles = {
'offlineStyle': {'color': 'red'},
'onlineStyle': {'color': 'lightgrey'}
};
this.connectionStyle = styles.offlineStyle;
firebaseRef.child(".info/connected")
.on("value",
function(snap) {
this.status = snap.val();
if (snap.val()) {
console.log("Connected to DB.");
this.connectionStyle = styles.onlineStyle;
console.log(this.connectionStyle);
} else {
console.log("Disconnected to DB.");
this.connectionStyle = styles.offlineStyle;
console.log(this.connectionStyle);
}
console.log(this.status);
$rootScope.$broadcast('dbConnection:changed');
}
);
})
Controller
$scope.dbConnectionStatus = dbConnectStatus;
$scope.$on('dbConnection:changed', function() {
console.log("'on(...)' called. This is the $scope.dbConnectionStatus.connectionStyle:");
$scope.dbConnectionStatus = dbConnectStatus;
console.log($scope.dbConnectionStatus.connectionStyle);
console.log("This is the dbConnectStatus.connectionStyle:");
console.log(dbConnectStatus.connectionStyle);
});
$rootScope.$watch('dbConnectStatus', function (){
$scope.dbConnectionStatus = dbConnectStatus;
});
//$rootScope.$apply();
I then reloaded the code and got this console message
I then turned off the connection
I then turn on the connection
It is clear to me that the service dbConnectionStatus isn't updated as a global variable in the way that I expected. I was on the assumption that a service is called once when the application is load and that assigning a scope variable to a service (object) is not a call but a reference...
What am I doing wrong?

I worked in a jsFiddle using $emit and $on to handle the status changes inside the service. The main problem is that when going online the angular binding was not working properly so I needed to force an angular cycle with $scope.$apply().
I started working on the first version of your code but made some refactoring. You can find the full code on the jsFiddle but the service and the controller look like the following:
Service
.service('dbConnectStatus', function($rootScope){
var status = false;
var color = 'red';
var self = {
startWatchingConnectionStatus: function(){
var connectedRef = firebase.database().ref().child(".info/connected");
connectedRef.on("value", function(snap) {
console.log(snap.val());
status = snap.val();
if (status) {
color = 'blue';
console.log("Connected to DB (" + color + ")" );
} else {
color = 'red';
console.log("Disonnected to DB (" + color + ")" );
}
$rootScope.$emit('connectionStatus:change', {style: {'color': color}, status: status}});
});
},
getStatus: function(){
return status;
},
getColor: function(){
return color;
}
};
return self;
})
Controller
.controller('HomeCtrl', ['$scope', 'dbConnectStatus', '$rootScope',function($scope, dbConnectStatus, $rootScope) {
dbConnectStatus.startWatchingConnectionStatus();
$rootScope.$on('connectionStatus:change', function currentCityChanged(event, value){
$scope.color = value.style;
//if changed to connected then force the $apply
if(value.status === true){
$scope.$apply();
}
});
}]);
Let me know if there is anything that is still not clear.

Inspired from #adolfosrs great answer I found the following solution to work for me.
Service
.service('dbConnectStatus', function(firebaseRef, $rootScope){
// Initial setup
var styles = {
'offlineStyle': {'color': 'red'},
'onlineStyle': {'color': 'skyeblue'}
};
// Functions to switch status
var offline = function () {
this.boolean = false;
this.style = styles.offlineStyle;
}
var online = function () {
this.boolean = true;
this.style = styles.onlineStyle;
}
var get_status = function(){
return {
boolean: this.boolean,
style: this.style
}
}
// Read the firebase info and update when changed
firebaseRef.child(".info/connected")
.on("value", function(snap) {
if (snap.val()) {
online();
} else {
offline();
}
$rootScope.$emit('dbConnection:changed', get_status() );
});
})
Controller
// Hide it before the status is known.
$scope.dbConnectionStatus = {'color': 'transparent'};
// Getting and reading status changes
$rootScope.$on('dbConnection:changed', function(event, status) {
$scope.dbConnectionStatus = status.style;
$scope.$apply();
});

You should be able to get this working simply by storing the color in an object in the service and referencing it from the controller. e.g.
View
<button class="button button-icon ion-cloud" ng-style="dbStatusService.style"></button>
Controller
$scope.dbStatusService = dbConnectStatus;
Service
.service('dbConnectStatus', function(firebaseRef){
var status = false;
var style = {color: 'transparent'};
var connectedRef = firebaseRef.child(".info/connected");
connectedRef.on("value", function(snap) {
status = snap.val();
if (status) {
style.color = 'lightgrey';
} else {
style.color = 'transparent';
}
});
return {
'boolean': status,
'style': style
}
});

Related

Delay loading data in Angular JS

I have code like this
(function (app) {
app.controller('productListController', productListController)
productListController.$inject = ['$scope', 'apiService', 'notificationService', '$ngBootbox', '$filter'];
function productListController($scope, apiService, notificationService, $ngBootbox, $filter) {
$scope.products = [];
$scope.page = 0;
$scope.pagesCount = 0;
$scope.getProducts = getProducts;
$scope.keyword = '';
$scope.search = search;
$scope.deleteProduct = deleteProduct;
$scope.selectAll = selectAll;
$scope.deleteMultiple = deleteMultiple;
function deleteMultiple() {
var listId = [];
$.each($scope.selected, function (i, item) {
listId.push(item.ID);
});
var config = {
params: {
checkedProducts: JSON.stringify(listId)
}
}
apiService.del('/api/product/deletemulti', config, function (result) {
notificationService.displaySuccess('Deleted successfully ' + result.data + 'record(s).');
search();
}, function (error) {
notificationService.displayError('Can not delete product.');
});
}
$scope.isAll = false;
function selectAll() {
if ($scope.isAll === false) {
angular.forEach($scope.products, function (item) {
item.checked = true;
});
$scope.isAll = true;
} else {
angular.forEach($scope.products, function (item) {
item.checked = false;
});
$scope.isAll = false;
}
}
$scope.$watch("products", function (n, o) {
var checked = $filter("filter")(n, { checked: true });
if (checked.length) {
$scope.selected = checked;
$('#btnDelete').removeAttr('disabled');
} else {
$('#btnDelete').attr('disabled', 'disabled');
}
}, true);
function deleteProduct(id) {
$ngBootbox.confirm('Are you sure to detele?').then(function () {
var config = {
params: {
id: id
}
}
apiService.del('/api/product/delete', config, function () {
notificationService.displaySuccess('The product hase been deleted successfully!');
search();
}, function () {
notificationService.displayError('Can not delete product');
})
});
}
function search() {
getProducts();
}
function getProducts(page) {
page = page || 0;
var config = {
params: {
keyword: $scope.keyword,
page: page,
pageSize: 20
}
}
apiService.get('/api/product/getall', config, function (result) {
if (result.data.TotalCount == 0) {
notificationService.displayWarning('Can not find any record.');
}
$scope.products = result.data.Items;
$scope.page = result.data.Page;
$scope.pagesCount = result.data.TotalPages;
$scope.totalCount = result.data.TotalCount;
}, function () {
console.log('Load product failed.');
});
}
$scope.getProducts();
}
})(angular.module('THTCMS.products'));
So my problem is when i loading data the application take me some time to load data.
I need load data as soon as
Is the any solution for this?
Since you are loading data via api call, there will be a delay. To handle this delay, you should display a loading screen. Once the data is loaded, the loading screen gets hidden and your main screen is visible. You can achieve this using $http interceptors.
See : Showing Spinner GIF during $http request in angular
The api-call is almost certainly causing the delay. Data may be received slowly via the api-call so you could display any sort of loading text/image to notify the use that the data is being loaded.
If u want the data ready at the time when controller inits, u can add a resolve param and pass the api call as a $promise in the route configuration for this route.

Angularjs State transition

I am building a hybrid mobile app using ionic framework and cordova (first time).I am having problems with state transition because by default angular renders the template before completing the transition.This makes the the app look slow (when you click a menu item and wait for it to come).This happens only for those who load data from local storage or service! My Question is: How can I make the template come empty in the moment I click the menu item , then show a loader until the template is ready.Below is some code is use in my menu controller for the state transition!
//I use ng-click="navigateTo('state name')"
$scope.navigateTo = function (stateName) {
$timeout(function () {
$mdSidenav('left').close();
if ($ionicHistory.currentStateName() != stateName) {
$ionicHistory.nextViewOptions({
disableAnimate: false,
disableBack: true
});
$state.go(stateName);
}
}, ($scope.isAndroid == true ? 1000 : 0));
};// End navigateTo.
Below is the controller code for the view that needs a solution
appControllers.controller("calendar_Ctrl", function($scope,$rootScope, $state,$stateParams, $ionicHistory, $filter, $q, $timeout, $log, MaterialCalendarData, $moment) {
$scope.isAnimated = $stateParams.isAnimated;
$scope.selectedDate = null;
$scope.weekStartsOn = 0;
$scope.dayFormat = "d";
$scope.disableFutureDates = false;
$scope.directionn = "horizontal";
$scope.setDirection = function(direction) {
$scope.directionn = direction;
$scope.dayFormat = direction === "vertical" ? "EEEE, MMMM d" : "d";
};
$scope.dayClick = function(date) {
$scope.msg = "You clicked " + $filter("date")(date, "MMM d, y h:mm:ss a Z");
};
$scope.setContentViaService = function() {
var today = new Date();
MaterialCalendarData.setDayContent(today, '<span> :oD </span>')
}
$scope.getItems = function(){
if(localStorage.getItem("eventsData")){
var eventsData = localStorage.getItem("eventsData");
return JSON.parse(eventsData);
}else{
return [];
}
}
var events = $scope.getItems();
// You would inject any HTML you wanted for
// that particular date here.
var numFmt = function(num) {
num = num.toString();
if (num.length < 2) {
num = "0" + num;
}
return num;
};
var loadContentAsync = false;
$log.info("setDayContent.async", loadContentAsync);
$scope.setDayContent = function(date) {
var key = [date.getFullYear(), numFmt(date.getMonth()+1), numFmt(date.getDate())].join("-");
var data = (events[key]||[{ type: ""}]);
if (loadContentAsync) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(data);
});
return deferred.promise;
}
return data;
};
$scope.isAnimated = $stateParams.isAnimated;
});
Thank You Very Much for your time and help!!
Hi Use $ionicLoading Service to solve this problem,
http://ionicframework.com/docs/api/service/$ionicLoading/

View doesn't get updated from Service

I am trying to display an object (songTitle) from my service. The initial state (tmp) is displayed. If I am changing the object in the service, the view doesnt get updated.
Js:
var party = angular.module("party", []);
party.run(function () {
var tag = document.createElement('script');
tag.src = "http://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
});
party.service('PlayerService', function ($window) {
this.playlist = [
"https://www.youtube.com/watch?v=fnW2uLwHAas",
"https://www.youtube.com/watch?v=iPT8DA32U6U",
"https://www.youtube.com/watch?v=eGjEnfQl37s",
"https://www.youtube.com/watch?v=nFtTY2S20mI",
"https://www.youtube.com/watch?v=UmXQiPLoLTk",
"https://www.youtube.com/watch?v=PbVx85DS9zc",
"https://www.youtube.com/watch?v=ciidn3nEoiE",
"https://www.youtube.com/watch?v=sm0DgkBEnUI",
"https://www.youtube.com/watch?v=J2OCSWF7sAw",
"https://www.youtube.com/watch?v=y_-giRHtuv8",
"https://www.youtube.com/watch?v=iPT8DA32U6U",
"https://www.youtube.com/watch?v=eGjEnfQl37s",
"https://www.youtube.com/watch?v=nFtTY2S20mI",
"https://www.youtube.com/watch?v=UmXQiPLoLTk",
"https://www.youtube.com/watch?v=PbVx85DS9zc"
];
this.player = {};
this.pbTimer = null;
this.songTitle = "tmp";
$window.onYouTubeIframeAPIReady = function () {
this.player = new YT.Player('ytplayer', {
height: '100',
width: '100',
videoId: 'ciidn3nEoiE',
events: {
'onReady': onPlayerReady
}
});
}
function onPlayerReady() {
console.log("db ready");
songTitle = player.getVideoData().title;
console.log(songTitle);
}
this.playVideo = function (url) {
console.log("db playVideo " + url);
player.loadVideoById(url.split("watch\?v=")[1], 0, "large");
console.log(player);
}
});
party.controller("FrontController", function ($scope) {
$scope.front = {};
$scope.front.title = "PARTY";
});
party.controller("PartyController", ['$scope', 'PlayerService', function ($scope, PlayerService) {
$scope.party = {};
$scope.party.title = "PARTY";
Sortable.create(playlist, { /* options */ });
$scope.playlist = PlayerService.playlist;
$scope.playVideo = function (url) {
PlayerService.playVideo(url);
}
$scope.songTitle = PlayerService.songTitle;
}]);
HTML
<body ng-app="party">
<div ng-controller="PartyController" class="container-fluid">
...
<p id="playertitle">{{songTitle}}</p>
...
Log:
db ready
Blackmill Feat. Veela - Life (Full Version)
The problem is in your onPlayerReady function. The line songTitle = player.getVideoData().title; doesn't set songTitle on your service, but rather on the global scope, which is the window object. Simply using this.songTitle won't help either, because this doesn't refer to your service too in the scope of onPlayerReady.
The easiest solution would be to save a reference to your service outside of onPlayerReady and then use it to assign songTitle:
var self = this;
function onPlayerReady() {
console.log("db ready");
self.songTitle = player.getVideoData().title;
console.log(self.songTitle);
}
Still, this is not enough. Because you change songTitle from outside the Angular world (the Youtube player callbacks), you need to call $scope.$apply to notify Angular something has changed.
For that, you need to inject $rootScope into your service:
party.service('PlayerService', function ($window, $rootScope)
and change songTitle using $rootScope.$apply:
var self = this;
function onPlayerReady() {
console.log("db ready");
$rootScope.$apply(function() {
self.songTitle = player.getVideoData().title;
console.log(self.songTitle);
});
}

$watch not updating scope variable

First I want to say that I am a complete beginner in AngularJS and just attempting to understand the basic concepts. I have a background in Java and PHP.
I am building a part of a website. Right now the angular app only consists of opening and closing 2 drop down menus registrationDropDown and loginDropDown. I want them to work so that only one can be open at a time ie. if I open one, and the other is already open, the older one is forced to close.
I have a service to manage the variables that determine whether the drop downs should be open or closed and 2 controllers, one for login and one for registration, both include $watch for the respective variables.
THE PROBLEM
I want the app to work so that only one of the drop downs can be open at one time.
JSFIDDLE: http://jsfiddle.net/F5p6m/3/
angular.module("ftApp", [])
.factory('dropDownService', function () {
var loginDropDownStatus = false;
var registrationDropDownStatus = false;
return {
getLoginDropDownStatus: function () {
return loginDropDownStatus;
},
showLoginDropDown: function () {
console.log("showing login drop down");
registrationDropDownStatus = false;
loginDropDownStatus = true;
console.log("loginDropDownStatus" + loginDropDownStatus + "registrationDropDownStatus" + registrationDropDownStatus);
},
hideLoginDropDown: function () {
console.log("hiding login drop down");
loginDropDownStatus = false;
console.log("loginDropDownStatus" + loginDropDownStatus);
},
getRegistrationDropDownStatus: function () {
return registrationDropDownStatus;
},
showRegistrationDropDown: function () {
console.log("showing registration drop down");
registrationDropDownStatus = true;
loginDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
},
hideRegistrationDropDown: function () {
console.log("hiding registration drop down");
registrationDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
}
};
}) .controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.loginDropDownStatus = dropDownService.getLoginDropDownStatus();
$scope.$watchCollection('loginDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("LOGIN new value is " + newValue);
$scope.loginDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.loginDropDownStatus == false ) {
dropDownService.showLoginDropDown();
dropDownService.hideRegistrationDropDown();
$scope.loginDropDownStatus = true;
} else if ( $scope.loginDropDownStatus == true ) {
dropDownService.hideLoginDropDown();
$scope.loginDropDownStatus = false;
}
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.registrationDropDownStatus = dropDownService.getRegistrationDropDownStatus();
$scope.$watch('registrationDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("new value is " + newValue);
$scope.registrationDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.registrationDropDownStatus == false ) {
dropDownService.showRegistrationDropDown();
dropDownService.hideLoginDropDown();
$scope.registrationDropDownStatus = true;
} else if ( $scope.registrationDropDownStatus == true ) {
dropDownService.hideRegistrationDropDown();
$scope.registrationDropDownStatus = false;
}
};
})
Edit:
Here is probably the shortest option:
angular.module("ftApp", [])
.controller("ctrl", function ($scope) {
$scope.toggle = function(menu){
$scope.active = $scope.active === menu ? null : menu;
}
})
FIDDLE
One controller, no service.
Previous Answer:
I think you have quite a bit of code to get something very simple done. Here is my solution:
angular.module("ftApp", [])
.service('dropDownService', function () {
this.active = null;
this.toggle = function(menu){
this.active = this.active === menu ? null : menu;
}
})
.controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("login");
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("reg");
};
})
FIDDLE
You can make it even shorter by only using one controller. You wouldn't even need the service then.
You are overcomplicating things. All you need your service to hold is a property indicating which dorpdown should be active.
Then you can change that property's value from the controller and check the value in the view to determine if a dropdown should be shown or hidden.
Something like this:
<!-- In the VIEW -->
<li ng-controller="XyzController">
<a ng-click="toggleDropdown()">Xyz</a>
<div ng-show="isActive()">Dropdown</div>
</li>
/* In the SERVICE */
.factory('DropdownService', function () {
return {
activeDropDown: null
};
})
/* In the CONTROLLER */
controller("XyzDropdownController", function ($scope, DropdownService) {
var dropdownName = 'xyz';
var dds = DropdownService;
$scope.isActive = function () {
return dropdownName === dds.activeDropdown;
};
$scope.toggleDropdown = function () {
dds.activeDropdown = (dds.activeDropdown === dropdownName) ?
null :
dropdownName;
};
})
See, also, this short demo.
Based on what exactly you are doing, there might be other approaches possible/preferrable:
E.g. you could use just on controller to control all dropdowns
or you could use two instances of the same controller to control each dropdown.
See my updated fiddle. I simplified the code and removed the service. Because you just used two variables to control visibility, you don't need a service nor $watch. You need to keep variables in the $rootScope, otherwise changes in a controller is not visible to another controller due to isolated scopes.
angular.module("ftApp", [])
.controller("LoginDropDownController", function ($scope, $rootScope) {
$rootScope.loginDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.loginDropDownStatus == false) {
$rootScope.registrationDropDownStatus = false;
$rootScope.loginDropDownStatus = true;
} else if ($rootScope.loginDropDownStatus == true) {
$rootScope.loginDropDownStatus = false;
}
};
}).controller("RegistrationDropDownController", function ($scope, $rootScope) {
$rootScope.registrationDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.registrationDropDownStatus === false) {
$rootScope.loginDropDownStatus = false;
$rootScope.registrationDropDownStatus = true;
} else if ($scope.registrationDropDownStatus === true) {
$rootScope.registrationDropDownStatus = false;
}
};
})
This code can be simplified further. I'll leave that to you.

Angular call service on asynchronous data

I have a service that make some calls to retrieve data to use in my app. After I've loaded data, I need to call another service to make some operations on my data. The problem is that second service will not have access to the data of the first service.
I've made a plunker: plunkr
First service
app.factory('Report', ['$http', function($http,$q){
var Authors = {
reports : [],
requests :[{'url':'data.json','response':'first'},
{'url':'data2.json','response':'second'},
{'url':'data3.json','response':'third'}]
};
Authors.getReport = function(target, source, response, callback) {
return $http({ url:source,
method:"GET",
//params:{url : target}
}).success(function(result) {
angular.extend(Authors.reports, result)
callback(result)
}
).error(function(error){
})
}
Authors.startQueue = function (target,callback) {
var promises = [];
this.requests.forEach(function (obj, i) {
console.log(obj.url)
promises.push(Authors.getReport(target, obj.url, obj.response, function(response,reports){
callback(obj.response,Authors.reports)
}));
});
}
return Authors;
}])
Second service
app.service('keyService', function(){
this.analyze = function(value) {
console.log(value)
return value.length
}
});
Conroller
In the controller I try something like:
$scope.result = Report.startQueue('http://www.prestitiinpdap.it', function (response,reports,keyService) {
$scope.progressBar +=33;
$scope.progress = response;
$scope.report = reports;
});
$scope.test = function(value){
keyService.analyze($scope.report.about);
}
I think this is what you are going for? Essentially, you want to call the second service after the first succeeds. There are other ways of doing this, but based on your example this is the simplest.
http://plnkr.co/edit/J2fGXR?p=preview
$scope.result = Report.startQueue('http://www.prestitiinpdap.it', function (response,reports) {
$scope.progressBar +=33;
$scope.progress = response;
$scope.report = reports;
$scope.test($scope.report.about); //added this line
});
$scope.test = function(value){
$scope.example = keyService.analyze(value); //changed this line to assign property "example"
}
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p>Progress notification : {{progress}}!</p>
<div ng-show="show">
<progress percent="progressBar" class="progress-striped active"></progress>
</div>
<pre>{{report}}</pre>
<pre>{{report.about}}</pre>
{{example}} <!-- changed this binding -->
</body>

Resources