I have trouble fetching one unique item from my firebase using angularfire 1.0.0. To clarify, I want my app to fetch a post given a unique firebase id e.g. "-JkZwz-tyYoRLoRqlI_I". It works when navigating in the app e.g. clicking on a link to a specific post, but not on a refresh. My guess is that it has something to do with synchronization. Right now it works when fetching all posts and use it in a ng-repeat. This is a clue to why it works for one item when navigating to the page. This should probably not be hard since this should be a pretty standard operation, but i can't get it to work. I have searched everywhere but there is actually no guide on this. In the API they refer to $getRecord(key)
Returns the record from the array for the given key. If the key is not
found, returns null. This method utilizes $indexFor(key) to find the
appropriate record.
But this is not working as expected. Or am i missing something?
It works for ng-repeat like this:
<div ng-repeat="postt in posts">
<div>
<h1>{{postt.title}}</h1>
<div>{{postt.timestamp}}</div>
<div>{{postt.content}}</div>
</div>
</div>
But not for unique items like this:
<div>
<h1>{{post.title}}</h1>
<div>{{post.timestamp}}</div>
<div>{{post.content}}</div>
</div>
This is the service:
'use strict';
angular.module('app.module.blog.post')
.factory("PostService", ["$firebaseArray", "FIREBASE_URL", function($firebaseArray, FIREBASE_URL) {
var ref = new Firebase(FIREBASE_URL + "posts");
var posts = $firebaseArray(ref);
return {
all: posts, // ng-repeat on this works fine
last: function(nr) {
var query = ref.orderByChild("timestamp").limitToLast(nr);
return $firebaseArray(query); // ng-repeat on this work fine to
},
create: function (post) {
return posts.$add(post);
},
get: function (postId) {
console.log(postId); // This is -JkZwz-tyYoRLoRqlI_I
var post = posts.$getRecord(postId);
console.log(post); // This print null
return post;
},
delete: function (post) {
return posts.$remove(post);
}
};
}]);
As the comments say in the get function, the postId is there and posts is also set, but the post is null.
This is the controller
'use strict';
angular.module('app.module.blog.post', [])
.controller('PostCtrl', ['$scope', '$routeParams', 'PostService', function($scope, $routeParams, PostService) {
// This returns e.g. postId "-JkZwz-tyYoRLoRqlI_I"
console.log($routeParams.postId);
$scope.post = PostService.get($routeParams.postId);
$scope.posts = PostService.all; // Illustrates the example not actually in this controller otherwise
}]);
This is what is an example on what is in the firebase database
<myfirebase>
posts
-JkUnVsGnCqbAxbMailo
comments
content: ...
timestamp: ...
title: ...
-JkZwz-tyYoRLoRqlI_I
comments
content: ...
timestamp: ...
title: ...
-JkhaEf9tQy06cOF03Ts
content: ...
timestamp: ...
title: ...
I find this problem very wierd since it should be very standard. I am obviously missing something, but can't work it out. Any help is very much appreciated!
Thanks in advance!
I know that the documentation of the $getRecord() function is kind of misleading. What you actually get from $firebaseArray is a promise of an array. It means that your posts variable will contain your posts at some point in the future. That being said, it seems that the $getRecord function only works when the promise have been resolved, i.e. when the array has been downloaded from Firebase. To make sure that the promise is resolved when you call the $getRecord function, you can use $loaded() on the promise :
var posts = $firebaseArray(ref);
posts.$loaded().then(function(x) {
var post = x.$getRecord(postId);
console.log(post);
}).catch(function(error) {
console.log("Error:", error);
});
If you are wondering why it works for ng-repeat, it's because Angular knows that the posts variable is a promise and waits for it to be resolved before rendering the values.
This is happening due to promises.
Along the lines of what Kato, Jean-Philippe said, $firebaseArray is not immediately available as it needs to be downloaded.
See the .$loaded() documentation:
.$loaded() "returns a promise which is resolved when the initial array data has been downloaded from Firebase. The promise resolves to the $firebaseArray itself."
That answers your question, and I just wanted to show another way of doing it:
This is a great use case for extending AngularFire services.
As the AngularFire API Documentation says:
"There are several powerful techniques for transforming the data downloaded and saved by $firebaseArray and $firebaseObject. These techniques should only be attempted by advanced Angular users who know their way around the code."
Putting all that together, you accomplish what you want to do by:
Extending the Firebase service $firebaseArray
Following the documentation for extending services.
Example
Here is a working JSFIDDLE example I put together that is tied to one of my public Firebase instances.
It's important to note that you should add ".indexOn":"timestamp" to your rules for /posts.
Factories
app.factory('PostsArray', function (FBURL, PostsArrayFactory) {
return function (limitToLast) {
if (!limitToLast) {
console.error("Need limitToLast");
return null;
}
var postsRef = new Firebase(FBURL + '/posts').orderByChild('timestamp').limitToLast(limitToLast);
return new PostsArrayFactory(postsRef);
}
});
app.factory('PostsArrayFactory', function ($q, $firebaseArray) {
return $firebaseArray.$extend({
getPost: function (postKey) {
var deferred = $q.defer();
var post = this.$getRecord(postKey);
if (post) {
console.log("Got post", post);
deferred.resolve(post);
} else {
deferred.reject("Post with key:" + postKey + " not found.");
}
return deferred.promise;
},
createPost: function (post) {
var deferred = $q.defer();
post.timestamp = Firebase.ServerValue.TIMESTAMP;
this.$add(post).then(function (ref) {
var id = ref.key();
console.log("added post with id", id, "post:", post);
deferred.resolve(ref);
}).
catch (function (error) {
deferred.reject(error);
});
return deferred.promise;
}
});
});
Controller
app.controller("SampleController", function ($scope, PostsArray) {
var posts = new PostsArray(5);
$scope.posts = posts;
$scope.newPost = {};
$scope.createNewPost = function () {
posts.createPost($scope.newPost);
}
$scope.postId = '';
$scope.getPost = function () {
posts.getPost($scope.postId).then(function (post) {
$scope.gotPost = post;
}).
catch (function (error) {
$scope.gotPost = error;
});
}
});
Related
I am working on displaying collection that I got from DB in angular with firebase DB. I have those controller and service setup. in the html, I use search.users expecting it will hold all the data that I got from the DB but it won't show up. I can't figure out why. I tried few things like angular.copy or $broadcast with no luck. Can anyone help advise on this? Appreciated in advance.
.controller('SearchController', function ($scope, SearchService, logout, $location){
var search = this;
search.users = SearchService.users;
//$scope.$on('evtputUsers', function () {
// search.users = SearchService.users;
//});
})
//service for SearchService
.factory('SearchService', function ($http, $rootScope){
var userRef = new Firebase("app url");
var broadcastUsers = function () {
$rootScope.$broadcast('evtputUsers');
};
//get the user info
//insert the data to the db.
//retrieving the data
var dbUsers;
userRef.child('users').on('value', function(snapshot){
dbUsers = snapshot.val();
// angular.copy(snapshot.val(), dbUsers);
console.log('usersinDB:',dbUsers);
broadcastUsers();
}, function(err){
console.error('an error occured>>>', err);
});
return {
users: dbUsers
};
})
Rather than using $broadcast() and $on() you should use the AngularFire module.
AngularFire provides you with a set of bindings to synchronizing data in Angular.
angular.module('app', ['firebase']) // 1
.controller('SearchCtrl', SearchCtrl);
function SearchCtrl($scope, $firebaseArray) {
var userRef = new Firebase("app url")
$scope.users = $firebaseArray(userRef); // 2
console.log($scope.users.length); // 3
}
There are three important things to take note of:
You need to include AngularFire as firebase in the dependency array.
The $firebaseArray() function will automagically synchronize your user ref data into an array. When the array is updated remotely it will trigger the $digest() loop for you and keep the page refreshed.
This array is asynchronous. It won't log anything until data has populated it. So if you're logs don't show anything initially, this is because the data is still downloading over the network.
hey guys, i have problem with get id. now i cant get '_id' from service.
this is my service
application.service('Arrears', [
function()
{
var db = new PouchDB('localhost:5984/arrears');
return {
get: function (_id) {
return db.get(_id)
.then(function (object) {
return db.remove(object)
.catch(function (err) {
console.log('error: ' + err); // isn't executed
});
})
.catch(function (err) {
console.log('error: ' + err); // isn't executed
});
},
}
}
]);
for insert data is ok.
now, this is my controller.
application.controller('ArrearsManagementAllController', ['$location', '$mdSidenav', '$scope', 'Arrears',
function($location, $mdSidenav, $scope, Arrears)
{
$scope.id = Arrears.get();
}
]);
and the last is my template.
<md-list-item
md-virtual-repeat="i in items"
ng-click="read(id)">
when i click read, my link show like this
localhost:8080/arrears_management/all/read/undefined
my code can't read the id.
please solve my problems. because im new using angularjs and pouchdb.
now the console show error like this :-
GET localhost/arrears/_id?_nonce=1447904157421 404 (Object Not Found)
wrong things with your code:
1.
get: function (_id) {
var id = get('_id')
},
does not return anything, and
2. get is calling a get method which is not defined
3. you don't have _id anywhere to return it. Maybe you want to put it in a service variable after you get it from the database?
OR, you probably wanted to do this:
get: function (_id) {
var id = db.get('_id');
return id;
},
note the db.get instead of just get and the very useful return. This may depend on the database engine, though...
EDIT
even in the controller you have a big issue: $scope.id = Arrears.get does a more complicated thing that you wish. You just need to replace it with:
$scope.id = Arrears.get();
in order to get the value of id because you were not calling the get method...
Let's say I have a service deal with Firebase operation:
angular.module('myApp').factory('taskOfferService', ['FURL', '$firebaseArray', '$firebaseObject', '$q', 'taskService', taskOfferService]);
function taskOfferService(FURL, $firebaseArray, $firebaseObject, $q, taskService) {
var ref = new Firebase(FURL);
var Offer = {
acceptOffer: function(taskId, offerId, runnerId) {
var offerRef = ref.child('taskOffers').child(taskId).child(offerId);
var taskUpdate = $q.defer();
offerRef.update({accepted: true}, function(error) {
if (error) {
console.log('Update offer accepted value failed!');
} else {
var taskRef = ref.child('tasks').child(taskId);
taskRef.update({status: "assigned", runner: runnerId}, function(error) {
if (error) {
console.log('Update task status failed!');
taskUpdate.reject(false);
} else {
taskUpdate.resolve(true);
}
});
}
});
return taskUpdate.promise;
},
};
return Offer;
}
})();
And I have a controller need to call this service and need to wait for a promise when update successful - to call the toaster.pop:
$scope.acceptOffer = function(offerId, runnerId) {
taskOfferService.acceptOffer($scope.selectedTask.$id, offerId, runnerId).then(function(){
toaster.pop('success', 'Offer is accepted.');
});
};
This code work and it is follow the suggestion from Firebase docs. But as you can see in my service, in order to get a promise, I need to use the update callback inside an update callback...My point is, Firebase SDK do not return a promise when done and I need to create this promise and it is A LOT OF CODES...
If I use firebaseObject (angularFire 1.0), it will run into issues listed here: saving new property overwrites firebase object
And using ANTIPATTERN according to #sinan, the code can be way clearer:
acceptOffer: function(taskId, offerId, runnerId) {
var o = $firebaseObject(ref.child('taskOffers').child(taskId).child(offerId));
o.$loaded().then(function(){
o.accepted = true;
o.$save().then(function(){
var oTask = $firebaseObject(ref.child('tasks').child(taskId));
oTask.$loaded().then(function(){
oTask.status = "assigned";
oTask.runner = runnerId;
oTask.$save();
});
})
},
The point is, using "ANTIPATTERN", I can utilize $save() - which return an promise so no $q service is need inside my firebase service. It looks a lot clearer in my opinion. Both method works.
BUT, according to doc:
"The $loaded() method should be used with care as it's only called once after initial load. Using it for anything but debugging is usually a poor practice."
I just find myself using $loaded A LOT! Please advice the best way to go about this.
Firebase has a JavaScript SDK that exposes the platform's functionality to JavaScript environments. AngularFire is a library on top of that SDK, that makes it easier to bind Firebase data to an AngularJS web interface.
Your example here is a plain data manipulation operation. You're not binding the data to the screen, so you should not need AngularFire. I also see no need for using promises.
As far as I can tell, this does the exact same thing as your last script:
acceptOffer: function(taskId, offerId, runnerId) {
var offer = ref.child('taskOffers').child(taskId).child(offerId);
offer.update({ accepted: true }, function() {
var task = ref.child('tasks').child(taskId);
task.update({ status: "unassigned", runner: runnerId });
});
}
Not only is this shorter, it also prevents downloading the data, just to then update it.
And the best part? Since AngularFire is built on top of the regular Firebase JavaScript SDK, they interoperate perfectly. So if in another somewhere you actually display the task or offer through an AngularJS view that watches to a $firebaseObject, it will show the updated value straight away.
Update
If you need to do something when the acceptOffer method is done saving, you can pass in a callback:
acceptOffer: function(taskId, offerId, runnerId, callback) {
var offer = ref.child('taskOffers').child(taskId).child(offerId);
offer.update({ accepted: true }, function(error) {
if (!error) {
var task = ref.child('tasks').child(taskId);
task.update({ status: "unassigned", runner: runnerId }, callback);
}
else {
callback(error)
}
});
}
You then invoke it like:
taskOfferService.acceptOffer($scope.selectedTask.$id, offerId, runnerId, function(error) {
if (!error) {
toaster.pop('success', 'Offer is accepted.');
}
else {
console.error('Something went wrong: '+error);
}
});
You could definitely also promisify the acceptOffer method. But this is not necessary.
I have a pretty standard app which will display news items from a remote JSON feed. So basically I have decided to poll the remote server and store the JSON in localStorage (to enable offline usage). For the moment, I have a manual page/view I must click on to update the localStorage , this works fine.
The problem is that after I use my temporary manual update page, I then go to the news page/view and it is not updated. To view the current JSON contents I must hit refresh (while still developing in the browser.)
I'm totally new to Angular and have tried to find solutions to this myself - $watch or reload: true seem to be suggested as fixes, but I cannot get them to work in my case.
Route
.state('tab.news', {
url: '/news',
reload: true,
views: {
'news-tab': {
templateUrl: 'templates/news_home.html',
controller: 'newsCtrl'
}
}
})
factory
angular.module('schoolApp.services', [])
.factory('newsService', function($q) {
var newsHeadlines =localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail status
var newsHeadlinesObj = JSON.parse(newsHeadlines);// convert to an object
console.log("factory newsService ran");
return {
findAll: function() {
var deferred = $q.defer();
deferred.resolve(newsHeadlinesObj);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var deferred = $q.defer();
var newsItem = newsHeadlinesObj[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
Controller
schoolApp.controller('newsCtrl', function($scope, newsService) {
console.log ( 'newsCtrl ran' );
newsService.findAll().then(function (newsHeadlinesObj) {
$scope.newsHeadlinesObj = newsHeadlinesObj;
}, function(error){
console.log(error)
});
})
Looking at my console, the first time I read the news, the factory then controller run, but if I go to pull more data down, then go hack to news, only the controller runs, unless I refresh, then both run again.
I do not need the news view to update 'live' while still on it (but if that can be easilly done all the better) - just to pick up new data when you go back to news after being elsewhere in the app.
Thank you.
Factories return singletons and only run once. The object newsService is cached by angular. The var declarations for newsHeadlines and newsHeadlinesObj will only ever run once; meaning your promise returning methods will always resolve the promise with the same data that was retrieved when your factory was first instantiated. You should put them in a function and call it from your find methods on the singleton object.
.factory('newsService', function($q) {
function getHeadlines() {
var newsHeadlines = localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail
return JSON.parse(newsHeadlines);// convert to an object
}
return {
findAll: function() {
var headlines = getHeadlines();
var deferred = $q.defer();
deferred.resolve(headlines);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var headlines = getHeadlines();
var deferred = $q.defer();
var newsItem = headlines[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
PS - I'm sure you know and are planning to do things differently later or something, but just in case you don't: Using promises here is pointless and you have no need for $q here. You could simply return the data instead of returning the promises.
I solved this withouut promises, I just used $rootScope in the factory and $scope.$on in the controller; when I change the factory, i use $rootScope.$broadcast to tell the controller that I change it.
.factory('dataFactory', ['$http', '$rootScope', function ($http, $rootScope) {
var dataFactory = {
stock: null,
getStock: getStock
}
function getStock() {
$http.get("/api/itemfarmacia/").then(function success(res) {
dataFactory.stock = res.data;
$rootScope.$broadcast('changingStock'); //Ones who listen this will see it
}, function error(err) {
console.log("Bad request");
})
}
return dataFactory;
}])
and in the controller
.controller('atencion', ["$scope", "$state", "dataFactory", function ($scope, $state, dataFactory) {
$scope.stock = dataFactory.stock; //At first is null
dataFactory.getStock(); //wherever you execute this, $scope.stock will change
$scope.$on('changingStock', function () {//Listening
$scope.stock = dataFactory.stock; //Updating $scope
})
}])
I asked the wrong question yesterday (and got a goodanswer that worked), but am realizing it's not what I needed. I need to be able to retrieve JSON data (preferably once), store it, and access it throughout my service. The challenge I'm having is that all the examples I can find talk about using JSON and passing to the app/controller, whereas in this case I need to get it, check it, and then it dictates what my module/service does.
For instance, I have my App and Controller, and then I have a module such as (this is psuedo-code, not meant to run):
angular.module("myModule")
.service("myService1", function($q, myService2, $http) {
this.getModel = function() {
return {
title: "My Title",
desc: "My Desc"
options: function () {
if (condition A)
return "option1";
else
return "option2";
}
};
};
})
.service("myService2", function($q, $http) {
this.getCfgInfo = function () {
var defer = $q.defer();
$http.get("my/json/url").then(function(response) {
defer.resolve(response.data);
});
return defer.promise;
};
})
In this example, I'm wanting to get the JSON, and use it within myService1 for both literal values (title, desc) as well as for conditions (condition A within the if).
I know I can do something like this (thanks to Joel for helping yesterday):
service("myService1", function($q, myService2, $http) {
// get a promise object for the configuration info
var cfgProm = rtDataMapper.getCfgInfo()
this.getModel = function() {
return {
title: cfgProm.then(function(response) {
return response.JSON_NAME;
}),
and it works fine as I've got the title mapped back into my model and there is a watch(), but I'm stumped as to how I get, store, and use the JSON within the service itself as a conditional (i.e. if (condition A) where condition A is coming from the JSON. Trying to wrap these in .then() doesn't seem to make sense, or at least I can't figure out how to do it.
I'm new to Angular and am attempting to modify some code that was left to us. I'm guessing I don't need the myService2 just to get the JSON. Can anyone help point me in the right direction? I've spent several hours online but can't seem to find a relevant reference/example.
Thanks
Live demo (click).
I'm having the service immediately get the data when it is injected (that code will only run once no matter how many times you inject it). That's nice because you won't have to call a function to get the data - it's called for when creating the service.
Your service method that returns that data will need to return the promise of the data, of course, since you aren't guaranteed that it will have come through when you ask for it. You can pass arguments to that method to use to determine your conditions. All you need to do for that is use promise.then in the method and resolve the promise with the modified data. Since that method is returning the promise already, the modification will be updated on the resolve. See all of this below and in the demo.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.getData(15).then(function(data) {
$scope.myData = data;
});
});
app.factory('myService', function($q, $timeout) {
//this code only runs once when you first inject the service
//get data immediately
var deferred = $q.defer();
$timeout(function() { //simulate ajax call
var data = { //ajax response data
foo: 15,
bar: 'Some data!'
};
data = modifyData(data, 1);
deferred.resolve(data);
}, 500);
function modifyData(data, fooVal) {
if (data.foo === fooVal) {
data.baz = 'Conditional data!';
}
return data;
}
var myService = {
//data can be modified when it comes from the server,
//or any time you call this function
getData: function(fooVal) {
if (fooVal) { //if you want to modify the data
deferred.promise.then(function(data) {
data = modifyData(data, fooVal);
deferred.resolve(data);
});
}
return deferred.promise;
}
};
return myService;
});