Storing and Accessing snapshot value from $rootScope - javascript

In my global controller (assigned to the body in html) I have the following code to grab a snapshot of firebase one time.
ref.once('value', function(snapshot){
var data = snapshot;
}).then(function(data){
$rootScope.content = data.val();
});
I am using the content in my HTML templates to render them. All the questions I found had resulted in putting the "then" code inside the callback.
Literally all I need to do is be able to grab a snapshot one time and store it on $rootScope.content and access it outside of the function, i.e. anywhere in my app.
I'm pretty sure it's because everything is rendering prior to firebase returning anything, I'm just not sure the best way to tell it to wait.
Thank you in advance.

Try:
ref.once('value', function(snapshot){
$rootScope.content = snapshot.val();
});
Or:
ref.once('value').then(function(snapshot){
$rootScope.content = snapshot.val();
});
From the once() docs:
Return Value
Returns a Promise that can optionally be used instead of the successCallback and failureCallback to handle success and failure.

I think your second line var data = snapshot; may be the problem? If all you want is to get the whole database and assign it to a object called $rootScope.content, or anything else, the below would work. I don't use angular but $rootScope.content would need to be an object, aka var foo = {} would be able to take snapshot.val(). Note that snapshot.val() is full of stuff other than just your JSON formatted data and you will have to extract out your individual nodes by calling the property names (the key's), e.g. snap.val().firstName to get Bob.
firebase.database().ref('/').once('value').then(function(snap) {
$rootScope.content = snap.val();
});

Related

Firebase Functions error: .once is not a function

I am trying to deploy a simple function to Firebase, but I am having some difficulties. Every time I try to use .once on a reference Firebase tells me that it is not a function. Here is my code
exports.testFunction = functions.database.ref('/Rooms/{firstID}/{pushId}/On').onWrite(event => {
const value = event.data.val();
var ref = functions.database.ref(roomNum);
return ref.once('value').then(snapshot => {
console.log(snapshot.numChildren);
return true;
}); });
I have also tried the following:
firebaseRef.once('value', function(dataSnapshot) {
console.log(snapshot.numChildren);
});
Nothing seems to work. Does anyone know of a fix or a different way of getting the number of children from a ref/snapshot?
Thank you.
functions.database.ref is a different object than the one you're used to using on the client. It's sole purpose is to listen for writes using it's only function, onWrite.
You can obtain your intended ref thru the event parameter.
var ref = event.data.ref
This is a reference to the path you specified in onWrite.
If you want the root reference:
var rootRef = event.data.ref.root
Further reading: https://firebase.google.com/docs/reference/functions/functions.database

cannot use http response array after putting it in $scope

I am using AngularJs in my application. I make a http call and getting the response. The response contains an array which I am putting as resultinside the $scope object. I am placing a watch on some attribute and try to access the stored object put inside the $scope object. If I print the result object, I see that it contains the array, but when I try to use the array properties such as length it throws an error. I am also not able to use other array methods such as results.data[0].
Please let me know where I am going wrong. Some code for understanding purpose:
var processResponse = function (result) {
$scope.results = result.data;
}
$scope.$watch('attribute', function(newVal) {
console.log($scope.results.length)
});
Depend how you call this. If the result is a promisse you can try do it:
result.then(successCallback, errorCallback);
Just try to declare/init $scope.results = []; at the top of your controller.
Then the method length couldn't crash ;)
Since it was asynchronous request, I had to take care of null by the following code:
if ($scope.results && $scope.results.data){
console.log($scope.results.data.length);
}

Factory returning same value

I am trying to get songs from soundcloud, I am using some input to set value and send it to my factory to get all the related list of songs and display it.
The issue is the the first time all works correctly, but when I am trying to input new values I am getting same results as first time.
My code looks like:
.controller('DashCtrl', function ($scope, SongsService) {
$scope.formData = {};
$scope.searchSong = function () {
SongsService.setData($scope.formData.songName);
};
UPDATE
the factory :
.factory('SongsService', function ($rootScope) {
var List = {};
List.setData = function (tracks) {
var page_size = 6;
SC.get('/tracks', {limit: page_size, linked_partitioning: 1}, function (tracks) {
// page through results, 100 at a time
List = tracks;
$rootScope.$broadcast('event:ItemsReceived');
});
};
List.getItems = function () {
return List;
};
return List;
}).value('version', '0.1');
Thanks for help!
It's hard to tell without a plunkr reproducing the issue and showing all your relevant code, but I think your problem is that you're overwriting the List variable in the async answer, and this List (I assume) is the object you originally returned from your factory.
I see two noteworthy concepts here:
the fact that angular factories are effectively singletons
and that javascript objects are passed by reference-by-value (see call-by-sharing, or one of many stackoverflow discussions).
An angular factory is a singleton, meaning the factory function will only be called once, before the first injection, and every controller it's injected into will work with the same object reference it returned. If you overwrite this object reference, well, the previous value (which the controller has) is still a reference to the original object.
Edit: In fact, by overwriting List you're creating a new object which doesn't even have a setData method anymore!
You probably want to make List private to SongsService, and return a more complex object from the factory that captures List in a closure, and offers some public getter/setter methods for it. (If you insist on replacing the contents of the returned List variable, empty the object and extend it with the new properties - including this method again. But this is much more work, and not a nice solution.)
In Angular Service constructors and Factory methods are singleton objects. You need to return a method that you can call. Your code examples are incomplete so it is hard to tell what is going on. What is returned by your factory method, the List object?
If so, when the first call is completed, it overwrites the List object so that the setData method can't be called a second time. What is the SC object, I can not see in your example how you are injecting it. You probably want to fix that too.
Consider this possible solution.
Service
Songs.$inject = ['$http'];
function Songs($http) {
this.$http = $http;
}
Songs.prototype.getSongs = function(searchTerm) {
return this.$http.get('http://someendpoint/songs', {searchTerm: searchTerm});
}
service('songs', Songs);
Controller
DashController.$inect = ['songs'];
functionDashController(songs) {
this.songs = songs;
this.results = [];
}
DashController.prototype.searchSongs = function(searchTerm) {
var self = this;
this.songs.getSongs(searchTerm).then(function(results) {
this.results = results;
});
}
controller('DashController', DashController);
This is example uses the best practice controllerAs syntax explained here: http://toddmotto.com/digging-into-angulars-controller-as-syntax/
I found the issue,
I got same results all the time because I didnt use cooreclty the api of soundcloud, I didnt send the title on the api... also you are correct, I should not set the list as empty..I should set some value to the list...

Why does it work correct with an array but not with an object?

I am developing an AngularJS application and found the following behavior.
I have two functions in my service. The first function returns all the categories stored in the database and the second returns one category by its id.
Here is my service:
angular.module('categoriesRepository', [])
.service('categoriesRepository', ['$cordovaSQLite', 'sqliteHelper',
function ($cordovaSQLite, sqliteHelper) {
//this works - returns an array with all categories
this.getAll = function () {
var categories = [];
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories;")
.then(function (res) {
for (var i = 0; i < res.rows.length; i++) {
categories.push(res.rows[i]);
}
});
return categories;
}
//this works not - returns undefined
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
category = res.rows[0];
});
return category;
}
}]);
I know that I can use Angulars $q to run functions asynchronously, and use their values when they are done processing.
Why does the getById function return the category directly and the getAll wait until the array is filled?
EDIT
I had the getAll function posted wrong. There is no return statement before $cordovaSQLite.execute
UPDATE:-
After your question is updated.
In the first example your are creating an array first by doing var categories = [];and then returning this array before finishing your async call. When your async call completes it just pushes certain elements into the array thus not destroying the reference to the array (categories ) variable. When it is returned back if you will debug it you will find the function returning an empty array and later when the async call succeeds only then the array will be filled.
In the second example you are creating just a variable and then returning it before the async call finishes. But then the async call is finished you assign the variable to a new value. thus destroying the earlier reference.
Solution:-
Though not a preffered approach to make it work. you will have to maintain the category variable reference. for this you can use angular.copy OR angular extend
So the second part of your code should be like
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
angular.copy(res.rows[0], category);
//now the reference to the category variable
//will not be lost
});
return category;
}
Better Practice:-
The way you have been developing this application is wrong. Async calls should not be handled this way. I earlier asked a question just to clarify the way to handle the async calls and state inside the angular app, factories and controllers please have a look here. It provides two ways to handle the state and async calls. There might be many more practices out there but these two suit me best.
It is unfortunate that this approach appears to 'work' because it is caused by the modification of the returned array object "at some unspecified time" after it is returned.
In the usage the array is accessed/observed after1 it has been modified by the asynchronous call. This makes it appear to function correctly only because of the (accidental) asynchronous-later-than observation.
If the observation was prior to the actual completion of the SQLite operation - such as immediately after the getAll function call - it would reveal an empty array.
Both functions are incorrectly written and the first accidently releases Zalgo (or perhaps his sibling).
See How do I return the response from an asynchronous call? for more details.
1 Chrome's console.log can be confusing as it works like console.dir and thus may be showing the current value and not the value when it was invoked.
As stated already, this is a bad approach. You can't use result of your function immediately after it returns.
However I didn't see the answer to your exact question: why do they behave differently?
It happens because with an array you return a reference to an object (type Array). Later on you use same reference to modify contents of the object, i.e. push new items into the array.
However in second function you modify the reference itself. You make you local variable categories point to a new object. Thus old object (reference to which was returned to outer scope) remains untouched. To make it work the same way you should have written
category.row = res.rows[0];
You return the result of the execute in the first case, whereas you return the variable in the second case, which has most likely not been populated yet.

How do I access data in this javascript MAP object?

I'm making an AJAX call which returns XML data, and this is my 'success:' function (callback):
success: function (data) {
var $rowArray = $(data).find("[nodeName=z:row]");
$rowArray.each(function(index) { // for each date put it into calMap.
calMap[$(this)[index].title] = $(this).attr("ows_Title");
calMap[$(this)[index].date] = $(this).attr("ows_EventDate");
});
}
calMap is a global javascript object declared outside of the function.
var calMap = {};
What I want to do is create a function where I can pass in a title, have it search calMap for that title, and if found, the specific object is returned and I'll be able to access the date property for that object.
Problem is, I can't seem to access the data I insert into the calMap object. For starters, I just want to print the map. Tried eval'ing it, tried alerting calMap[0], tried alerting calMap[0].title, but nothing. Can someone help me with this? Thanks!
Update:
I want to do something like this:
var data = getData("myTitle");
function getData(title) {
// if title is in calMap, something like this?
var result = (calMap[title]));
return result; // returns an object or NOTHING
}
then i'll check if date is defined or not, and if it is, i'll access its properties (ie. data.date. That make sense?
ANSWER:
I ended up using an array. STILL think I should be able to use the object MAP, but needed to get my project done.
Here's the final code for the code that accesses the array items:
function hasCalDate(code)
{
var matched = "";
for (var f=0;f<calMap.length;f++){
var re = new RegExp(code);
if (re.test(calMap[f].title))
{
matched = calMap[f].title+','+calMap[f].date;
}
}
return matched;
};
Thanks everyone.
You need to initialize calMap as an array (i.e. square brackets, not curly ones):
var calMap = [];
Then, inside your each function, I'm guessing you want something more like
calMap.push({
title: $(this).attr("ows_Title"),
date: $(this).attr("ows_EventDate")
});
Your problem is that the success function is only run when your AJAX request completes. If you want to access calMap safely, you need to do so inside your callback.

Categories

Resources