I have started to use angularjs and now am following a tutorial about angularjs with angularfire. In the tutorial there is a section to learn how to use child_added
angular.module('firebaseApp').service('MessageService', function(FBURL, $q, $firebase) {
var messageRef = new Firebase(FBURL).child('messages');
var fireMessage = $firebase(messageRef).$asArray();
return {
childAdded: function childAdded(limitNumber, cb) {
fireMessage.$on('child_added', function(snapshot){
console.log(snapshot);
var val = snapshot.val();
cb.call(this,{
user: val.user,
text: val.text,
name: snapshot.name()
});
});
},
and main controller :
MessageService.childAdded(5, function(addedChild){
$timeout(function() {
$scope.messages.push(addedChild);
});
});
When I use the code, chrome developer tools is giving an error :
TypeError: undefined is not a function
adressing .$on in MessageService and .childadded in controller.
This is a video tutorial and I can see him working like this. I checked many answers but could not find a way. My best shot is I guess different versions of angularjs and angularfire. But that did not help me either. Can you help me to solve it?
Thank you in advance.
I thought I'd offer a bit of clarity on the topic for those who are taking this tutorial on tutsplus. First, we should pay attention to the versions of firebase and angularfire that we are using, compared to what the instructor is using. This might seem obvious, but there are many out there who fail to realize it. Whenever watching a tutorial, you should always check the version of ANY package you are using. This world of software moves really fast, and up-to-date tutorials are a luxury.
Pay attention to your bower.json file in your project. Note the version of the following lines:
"firebase": "~2.0.4",
"angularfire": "~0.9.0"
You can see here the version of the dependencies I used. Chances are, you were like me, and your version will differ from what you find in the teacher's version. The instructor offers his source files on the course main page. Download those files, which should be titled SourceFiles_RTWebAppAngularFirebase.zip. Once downloaded, open it, and you will see a list of packaged zip files contained in a file called Firebase Source. Find lesson-16-angularfire.zip, and also open it. A folder titled "Firebase" should be provided, and there you will find the instructor's app that he created during the tutorial. Check out his bower.json file, and check it against what you have. According to what I found, both versions of firebase and angularfire were vastly different. This means only one thing: we have some reading to do.
Go here and check out the releases of AngularFire: https://github.com/firebase/angularfire/releases
Scroll down, and read the release notes of v0.8.0. A very important note is found here.
$on() and $off() no longer exist. Similar functionality can be obtained with $watch() but should be discouraged for trying to manually manage server events (manipulation should be done with data transformations through $extendFactory() instead).
Obviously now, we understand that we can't follow blindly in the instructor's footsteps. We have to try mimicking his app's functionality. Reading what I posted above, we can use $watch to obtain similar functionality. However, I honestly tried to make this work, and the functionality that $watch delivers is not exactly the functionality we are trying to achieve. Ideally, we should synchronize our empty messages array $scope.messages with $firebase(messageRef).$asArray(). That wouldn't be as fun though.
The route I took was this: I looked at what cb.call() was doing, which is the following
MessageService.childAdded(function(addedChild) {
$scope.messages.push(addedChild);
});
This call back function pushes an object created from cherry picked values obtained from a snapshot returned from a 'child_added' event. The real leverage here is the snapshot, which is provided by the $firebase module itself with Firebase.on(). If you read the docs for the "Child_Added" event, it says
This event will be triggered once for each initial child at this location, and it will be triggered again every time a new child is added. The DataSnapshot passed into the callback will reflect the data for the relevant child
The DataSnapshot returned for every "initial" child at this location, is what initially populates the list of messages. This means that we can change this:
fireMessage.$on('child_added', function(snapshot){
console.log(snapshot);
var val = snapshot.val();
cb.call(this,{
user: val.user,
text: val.text,
name: snapshot.name()
});
});
back to this:
messageRef.on('child_added', function(snapshot) {
var val = snapshot.val();
cb.call(this,
user: val.user,
text: val.text,
name: snapshot.key()
});
});
The messages should populate again on the front page. Your code should be stable enough as well to follow the instructor during the remainder of the Refactor lesson.
Related
I have an ng-repeat which is connected to a firebaseArray which is an index of a user's doc Ids.
I want to display a list of their docs with additional info for each one, e.g. it's title and description.
My firebase structure is:
+users
- userId1
-theirDocs
-docId1: true
-docId2: true
+docs
-docId1
- tile
- description
- url
-docId2
- etc etc
I've tried everything and still can't get this to work, it seems a pretty common use case. I've tried using a function that calls a firebaseObject each time. I've tried using a separate controller. I've tried a directive, I've even tried using firebase.utils library (I think out of date now).
Can anyone recommend the best way to do this using AngularFire? Thanks in advance!
It would be better to see a code snippet as to fully understand what you've tried first of all, as it's difficult to determine where the issues lie. That being said, as long as your rules are not preventing this, you may need to wait for the data to download using $loaded function beforehand.
EXAMPLE
This example is clearly available by following this link, yet here is an example for further clarity:
var yourFirebaseReference = firebase.database().ref("REPLACE WITH YOUR FIREBASE BRANCH HERE");
var data = $firebaseArray(yourFirebaseReference);
data.$loaded().then(function(x) {
console.log(data) // Example, you can do what you want with your 'data' now as the data has loaded
x === data; // true
}).catch(function(error) {
console.log("Error:", error);
});
I have a simple bit of code adding an object to a $firebaseArray() like this:
var itemRef = database.ref('/somelist/of/items');
$scope.list = $firebaseArray(itemRef);
$scope.list.$add({name:"new item1"}).then(function(newItem) {
console.log("I wish this log would show up");
}).catch(function(er) {
console.log("This doesn't run either");
}).finally(function(res) {
console.log("Added just to be sure, but also doesn't get here");
});
I've initialized firebase, and the $add() function works perfectly (as well as all of the other features, including Auth and Storage).
However, I need the key for the added item and the promise is never executed. My original attempt was based on the link below but I cannot even execute the simplified code above.
The original attempt:
https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebasearray-addnewdata
I tried to set up a plunker or jsfiddle to demo the error but, I feel like an idiot because I'm getting X-origin issues on both platforms.
I spoke directly with the AngularFire folks and there is a bug with version 2.3.0 where it has compatibility issues with firebase 3.7.1.
For ref:
https://github.com/firebase/angularfire/issues/922
I'm using angular-ui.github.io/angular-google-maps/
I use the recommended lazy loading technique, I have this in my app.js:
.config(function(uiGmapGoogleMapApiProvider) {
uiGmapGoogleMapApiProvider.configure({
// key: 'your api key',
v: '3.20' //defaults to latest 3.X anyhow
//libraries: 'weather,geometry,visualization'
});
})
And at some point in my controller, I execute this:
var uiGmapGoogleMapApiTimer = $timeout(function() {
reject("uiGmapGoogleMapApi didn't respond after 10 seconds.");
}, 5000);
console.log('DUDE.01');
uiGmapGoogleMapApi.then(function(maps) {
console.log('DUDE.02');
$timeout.cancel(uiGmapGoogleMapApiTimer);
geocoder = new google.maps.Geocoder();
resolve(geocoder);
}, function(err) {
console.log('DUDE.03');
});
So, when I'm offline, the timer will kick in, and I send the user back to the login page. (Originally I simply exit the app. It works in android, but in iOS it doesnt work, it's in fact forbidden by Apple).
So... that's why now I'm sending it back to login page.
Now, in the login page, I reactivate my wifi.... and once I'm back in the page that (is supposed to) show the map.... it breaks. The success handler of uiGmapGoogleMapApi.then never gets called. (Dude.01 gets printed, but Dude.02 nor Dude.03 get printed).
So... that page (wrongly) thinks that the device is still disconnected.
I suppose it's because the loading of google map javascripts is only done once (during load -- that´s why if I close my app, and return back, things will run just fine).
So... it's not really lazy loading (?). Or... if it's lazy loading, it doesn't seem to support scenarios like... try loading it the second time (if the first time failed because of connectivity).
Is anyone familiar enough with the source code of angular-ui.github.io/angular-google-maps/ to suggest what's the solution for this problem?
I looked at the logs I put in the library's code... I came to the conclusion: I need way to get line 183 to be re-executed on the second time I call uiGmapGoogleMapApi.then ... Currently it doesn't. It only gets called the first, when I start my app, and having internet connection since the beginning.
And based on what I understand from this doc on "provider", I need the uiGmapGoogleMapApi to be reinstantiated (right before) the second time I try to use it.
https://docs.angularjs.org/api/auto/service/$provide
Is that right? How?
Looks like I'm battling against provider caching here. Because I use dependency injection in my controller to get reference to uiGmapGoogleMapApi. (I have .controller('myctrl', function(uiGmapGoogleMapApi) {...})
what I might need is:
.controller('myctrl', function() {
var uiGmapGoogleMapApi = $injector.get('uiGmapGoogleMapApi');
//or something along that line maybe
})
I just tried it, still doesn't work.
Please help.
Thanks,
Raka
Well, my "solution" is: instead of going to login page, I simply refresh the app., by setting the current url of the window object to index.html.
I working on this angular-meteor tutorial step 12
an I have a question in
Stopping a subscription Topic
you can use ctrl+f using "meteorSubscribe"
then the key sentence on that topic is
The reason is that we are calling a different subscription on the same collection inside the partyDetails controller.
the code before correction is
$scope.party = $meteor.object(Parties, $stateParams.partyId).subscribe('parties');
$scope.users = $meteor.collection(Meteor.users, false).subscribe('users');
then after correction
$scope.party = $meteor.object(Parties, $stateParams.partyId);
$scope.users = $meteor.collection(Meteor.users, false).subscribe('users');
$scope.$meteorSubscribe('parties');
I try to run before correction code and nothing(error) show in cmd but it just cause the wrong result as tutorial say
if you navigate to the party details page and then go back, pagination and search will stop working.
Then i got two question
Why no error show on cmd?
Why error from partyDetails controller affect to partiesList controller search and pagination? What is their relation?
EDIT: If you don't cancel a subscription, then if you navigate away and back again you will end up trying to subscribe twice to the same publication, resulting in the error, because subscriptions in meteor last until you end them.
There are two ways to get rid of a subscription with angular-meteor. One you can assign a handle variable to the subscription and then on navigating away from the page you can stop it. Two (the recommended way) is to use $scope.$meteorSubscribe instead of $meteor.subscribe() because it is set up to automatically remove the subscription when the scope is destroyed.
I can't see all of your code to know for sure why you are or are not getting the errors you think you should, hopefully this sheds some light on what is going on in the tutorial.
The very end result would be something like:
$meteor.autorun($scope, function() {
$meteor.subscribe('parties', {
limit: parseInt($scope.perPage),
skip: parseInt(($scope.page - 1) * $scope.perPage),
sort: $scope.sort
}).then(function() {
$scope.partiesCount = $meteor.object(Counts, 'numberOfParties', false);
$scope.parties = $meteor.collcetion(function() {
return Parties.find({}, {
sort: $scope.getReactively('sort');
});
});
});
});
Notice that he's also changing the publish function on the server. It helps to understand if you click the links to show the git diffs.
I am using angularJS 1.3 with angularfire and firebase.
I did create some factories that encapsulate the firebase object, and it works very well. The 3 way data binding is really cool.
I am planning to build a large application using firebase for data persistance, but I am facing a massive issue :
In my business model, I want some models/collections to support 3-way data binding, but I want other collections not to be updated live.
Some clients visiting the resulting website might be scared when they see the website's content being updated live - and I don't want that.
Imagine you are reading an article , somehow long and complexe, and you put your attention into a paragraph which you have issues understanding, and suddenly, the paragraph disappears and is replaced by a new one. Now imagine that you have no idea about web development. You might start to think to consult some professionnal about your mental health, or think your computer is hacked. Who knows !
So I would like to disable the 3 way data binding for this article, and avoid this border-line client phone calls. The only solution I found would be not to use fireangular, and use the beyond angularfire documentation section instead, and remove the following lines :
ref.on("value", function(snapshot) {
$timeout(function() {
$scope.data = snapshot.val();
});
});
This would fix the issue, but then in my admin interface, I would have to implement a second Factory so admin users can access the 3 way data binding for articles - Which is not very DRY.
In my artitcle controller, I would have to write something like this :
$scope.loadArticle = function(){
if(UserService.type === 'admin'){
AdminArticleFactory.get(id);
}
else{
ArticleFactory.get(id);
}
}
Is there any other way of setting up firebase to disable the pull updates?
Yeah, that is what I also ran into. Sometimes you will repeat yourself with Firebase. Also when storing data. I would recommend just making a function to save the data that you don't want to be updated instantly.
<button ng-click="saveChanges">save</button>
In controller
$scope.saveChanges = function () {
var obj = $firebaseObject(ref);
obj.foo = "bar";
obj.$save().then(function(ref) {
ref.key() === obj.$id; // true
}, function(error) {
console.log("Error:", error);
});
}
edit
To desync a firebase object/array you can follow this answer: How to disassociate angularFire bound object?