I am new to AngularFire and am trying to understand a simple concept. If you go through the AngularFire tutorial located at the following url: http://angularfire.com/tutorial/index.html#gettingstarted there is a rudimentary example of using AngularFire to build a primitive "chat" application.
The tutorial is very clear and concise but I do not understand one primary point with it:
function MyCtrl($scope, angularFire){
$scope.messages = [];
var ref = new Firebase("https://<xxxxxx>.firebaseio.com/messages");
angularFire(ref, $scope, 'messages');
$scope.messages = []; //shouldn't this clear the data locally and remotely?
}
The issue is that a model is first created, and then the binding magic with AngularFire is setup such that there is now a 3-way binding to the model. If there is an array of data already stored in Firebase, that data is fetched and synced and your model will now have this data locally.
What I simply do not understand is, when the controller code runs, suppose I set the model to an empty array AFTER the angularFire binding is wired up, why doesn't the Firebase data get cleared out? Never mind the fact, that refreshing the page would basically keep wiping out the data (the behavior I want).
Now, I can get this behavior to work, if I wire up an ng-click event to a button, that calls a method named clear defined on my $scope object. If within, that method, I simply call: $scope.messages = [];, then my model is cleared locally, and remotely.
But why doesn't this work on initialization?
Help is always appreciated.
I think I may have found an answer to my own problem. It looks like, you must wait until the promise returns to actually start modifying the model like so. Now whenever I refresh my page, when my .then() runs, it will clear out my data.
I suppose this is how it should be done. Can anyone confirm?
$scope.messages = [];
var ref = new Firebase("https://<xxxxxx>.firebaseio.com/items");
var prom = angularFire(ref, $scope, 'messages');
prom.then(function(){
console.log("data loaded");
$scope.messages = [];
});
I'm guessing the remote data hasn't returned yet and is populated after your second call to $scope.messages =[]
edit:
Why not just explicitly remove your data from FB before binding it to a local list.
var ref = new Firebase("https://<xxxxxx>.firebaseio.com/messages");
ref.remove();
Related
While transitioning an existing angular site, I encountered an annoying problem. The initial symptom was that a certain controller was not running it's initialize function immediately following the login. I logged and I tracked, and eventually I realized it was a design flaw of the page. Essentially, index.html contains a <header>, <ng-view>, and <footer>. There are a couple of ng-if attributes that live in the header that I want to evaluate after the login, but since the view is the only thing that is reloaded, it was not reinitializing the header controller, and thus not updating the ng-if values.
Then I was reminded of ngInclude, which seems like the perfect solution, until I got it hooked up and realize that doesn't work either. It loads the template the first time, and doesn't reinitialize when the view changes. So then I got the bright idea of passing the HeaderController to another controller or service, and controlling this one stubborn boolean value through a proxy of sorts. That also didn't work. Then I tried putting a function and a boolean into another service, and mirroring that property in the header controller, but thus far I have not gotten this working.
I have done plenty of research about multiple views in the index, and so far I hear a lot about this ui-router, but I'm still not convinced that is the way I want to go. It does not seem to be a simple solution. I have not tried putting the ng-include into the templates yet either, because then I feel like that is going back in time to when we had to update 100 pages every time we changed the menu.
I lost a whole day to this. If anyone could tell me how to trigger the evaluation of this one property in my header controller which I would like to live outside the other templates, please let me know!
Ok so you need to know in your HeaderController when the view has reloaded. There's a number of ways of doing this but the easier and maybe the more correct in this particular case is with an event.
So when you are refreshing the view you just do this, let's say you need the new value of ob1 and ob2 variables.
// ViewController
$rootScope.$emit('viewRefresh', {ob1: 'newvalue1', ob2: 'newvalue2'});
And in your HeaderController you need to listen for that event, and set on your $scope the new values for those attrs (if you're not using controller as syntax).
// HeaderController
$rootScope.$on('viewRefresh', function onRefresh(event, data) {
$scope.ob1 = data.ob1;
$scope.ob2 = data.ob2;
})
Another Solution
Sharing a Promise through a Service (using $q)
function HeaderService($q) {
var defer = $q.defer();
return {
getPromise: function() {return defer.promise},
notify: function(data) {defer.notify(data)}
}
}
function HeaderController(HeaderService) {
var vm = this;
HeaderService.getPromise().then(function(data) {
vm.ob1 = data.ob1;
vm.ob2 = data.ob2;
})
}
function ViewController(HeaderService) {
var data = {ob1: 'newvalue1', ob2: 'newvalue2'};
HeaderService.notify(data)
}
I am trying to use the ebay api in an angular.js app.
The way the api works by itself is to pass data to a callback function and within that function create a template for display.
The problem that I am having is in adding the data returned from the callback to the $scope. I was not able to post a working example as I didnt want to expose my api key, I am hoping that the code posted in the fiddle will be enough to identify the issue.
eBayApp.controller('FindItemCtrl', function ($scope) {
globalFunc = function(root){
$scope.items = root.findItemsByKeywordsResponse[0].searchResult[0].item || [];
console.log($scope.items); //this shows the data
}
console.log($scope.items); //this is undefined
})
http://jsfiddle.net/L7fnozuo/
The reason the second instance of $scope.items is undefined, is because it is run before the callback function happens.
The chances are that $scope.items isn't updating in the view either, because Angular doesn't know that it needs to trigger a scope digest.
When you use the Angular provided async APIs ($http, $timeout etc) they have all been written in such a way that they will let Angular know when it needs to update it's views.
In this case, you have a couple of options:
Use the inbuilt $http.jsonp method.
Trigger the digest manually.
Option number 1 is the more sensible approach, but is not always possible if the request is made from someone else's library.
Here's an update to the fiddle which uses $http.jsonp. It should work (but at the moment it's resulting in an error message about your API key).
The key change here is that the request is being made from within Angular using an Angular API rather than from a script tag which Angular knows nothing about.
$http.jsonp(URL)
.success($scope.success)
.error($scope.error);
Option 2 requires you to add the following line to your JSONP callback function:
globalFunc = function(root){
$scope.items = root.findItemsByKeywordsResponse[0].searchResult[0].item || [];
console.log($scope.items); //this shows the data
$scope.$apply(); // <--
}
This method tells Angular that it needs to update it's views because data might have changed. There's a decent Sitepoint article on understanding this mechanism, if you are interested.
Am in the angular-fire-seed tutorial stage and experimenting with messages and child posts, for some bizarre reason I cannot see the children when I explicitly try to display them, but can see it when expanding on the parent node in the console. These messages display properly in the html where I have the ng-repeat, so I know I am getting the messages at least, albeit childless.
I thought angularfire-seed utils might be chopping or slicing some children so I reverted to straight firebase
This is what I have:
Code:
-----
var url = fbutil.ref() + "/messages/";
var ref = new Firebase(url);
var sync = $firebase(ref).$asArray();
console.log(sync); //this I can see as a proper $firebaseArray object
console.log(sync.length); //this displays as 0 even though length is 3 in object above
console.log(sync[1]); //displays as undefined
Data:
----
messages/id1/text
/id2/text
/id2/post
/id3/text
Thanks in advance for pointing out what I am mis-assuming !
You seem to be falling for the common asynchonisity problem.
This is your code fragment:
var sync = $firebase(ref).$asArray();
console.log(sync); //this I can see as a proper $firebaseArray object
console.log(sync.length); //this displays as 0 even though length is 3 in object above
console.log(sync[1]); //displays as undefined
The call to $asArray() doesn't return a Firebase array immediately, since the data may take some time to come down from the Firebase servers. So instead it returns the promise of a Firebase array, something that in the future will contain your data.
By the time you inspect the out of console.log(sync) in the console, the data will have downloaded so it is (as you say) a proper synchronized array. But when the console.log(sync.length) line runs, the data probably hasn't downloaded yet.
You can wait for the data to be downloaded with the $loaded() method. So:
var sync = $firebase(ref).$asArray();
console.log(sync);
sync.$loaded().then(function(sync) {
console.log(sync.length);
console.log(sync[1]);
}
got the answer from AngularFire Accessing child element methods
short answer: once assigned to a JS var, it is a POJO, not a $firebase object anymore.
Im finding myself fighting with 'pointers' in javascript. It's like I'm in C++ again. I would like to know your approach to the following issue Im having.
My case: I have a ng-repeat that goes over a collection. When clicking one of the collection elements, I do a copy of the element, do changes over it and POST/PUT data to server.
If the server replies 200 then I will apply those changes to the collection. The collection contains objects, so it means that Im working with references NOT values.
The module definition with a service:
angular.module('dataModule', [])
.service('DataService', function () {
this.collection = [{id:0, who: 'I'},
{id:1, who: 'YOU'},
{id:2, who: 'HE'},
{id:3, who: 'SHE'},
{id:4, who: 'IT'}];
})
And here the controller:
.controller('listCtrl', function ($scope, $timeout, DataService) {
// Data used in view
$scope.collection = DataService.collection;
// Action trigger from the view
$scope.change = function(data, index){
// Get a copy
var copy = angular.copy(data);
// Apply changes over the copy
copy.id = data.id*100*index;
// Simulate POSTing/Updating data to server
console.log('Sending data to server...');
$timeout(function(){
// Response is 200
var response = 200;
// Assigning copy -> data
data = copy;
// The prev. assignment is not updating the collection
// Of course I could do $scope.collection[index] = copy;
// because this case is simple enough.
// Im finding myself having the service with methods find, edit, ...
// What's a better approach?
}, 2000)
}
});
As I said in the comments, Im finding myself implementing functions like find, edit or get in the service. Is that the approach to go???
Here is the jsfiddle http://jsfiddle.net/kitimenpolku/M66LV/5/ in case I was not able to explain it correctly.
From my perspective, there are two ways you can go with updating the view after you write to the server:
You ask the server for the entire collection again, and then set it
locally. This requires the use of promises and replacing the
current collection with a new one. This replacement will
automatically trigger an update in the ng-repeat directive you are
using in your template.
You perform the same transformation locally, as you mentioned yourself. This entails finding the correct index of the array and swapping out the old object for the edited one. This is how I like to setup my own applications and it's really not so hard to do. Here's how:
I can see that you really are thinking of this javascript replacement as if it were in c++. Here, you are correct in that data = copy; in your server callback is merely updating a reference, not any data itself.
Instead, you can alter the collection itself directly: $scope.collection[index] = copy
Here's a working fiddle: http://jsfiddle.net/wilsonjonash/GZ2eR/
data is a reference to the original item but using data = copy is just invalidating data so the original never gets updated.
So to update the original item you need to perform.
data.id = copy.id;
data.who = copy.who;
or
$scopy.collection[index] = copy
fiddle with updated code
http://jsfiddle.net/M66LV/7/
Here is my problem! I use $resource API to post data from the client side to the server side. After the server side successfully updates the database, I kinda hope my data in view will be updated.
I chose to achieve this with:
$scope.magazines = Magazines.query();
$scope.addMagazine = function() {
var magazine = new Magazines({...payload data});
Magazines.save(magazine, function() {
$scope.magazines = Magazines.query();
});
};
Magazines is a defined ngResrouce factory.
This code works fine. My only problem is, whenever I activate this function (through mouseclick), the view flashes (refreshes) once. It is a really really bad user experience.
I wonder if there is any other way to add the new "magazine" to the scope without refreshing the entire $scope.magazines ?
By the way, the api at the backend will add a few properties (like created time, or id to it). Even if I write code to get the last added item (a different factory method I suppose), I'll still have to add it to the $scope.magazines and I don't know how to do that.
Any thought?
Just push the new Magazine onto the existing $scope.magazines (assuming it's an Array)
var magazine = new Magazines({...payload data});
$scope.magazines.push(magazine);