Firebase Sync handling - angularjs

I have data structure like below:
-Company
|
-jskdhjJKHh-Yty
-companyName:CompanyTH
-Employees
|
-shdjasjdshy665-333
|
-empName:Peter
|
-Company
-jskdhjJKHh-Yty:true
Im pushing data of Employees like below in ParentController:
Step 1:
var ref=firebase.database().ref("Employees");
var newKey=ref.push({empName:"John"}).key
Setup 2:
var childCompany=ref.child(newKey+'/Company');
childCompany.set(true);
Step 3:
$scope.emplist=$firebaseArray(ref);
In HTML:
<div ng-repeat="emp in emplist" ng-Controller="ChildController">
<p>{{emp.empName}}</p>
<p>{{CompanyName}}</p>
</div>
In ChildController:
var companyRef=firebase.database().ref("Company/"+Object.keys($scope.emp.Company)[0]);
$scope.CompanyName=$firebaseObject(companyRef);
Problem is :
When Step 1 executed it sync data to $scope.emplist and ChildController executed for that ng-repeat instance and when code in ChildController try to execute line Object.keys($scope.emp.Company)[0] it gives error that Company is not defined. This error is beacause Step 2 is not executed and firebase sync data after Step 1.But when Step 2 is executes it updates firebase-database but ChildController does not executes on updatation of ng-repeat instance.
One Solution in my mind that somehow Can I stop Firebase to sync data until all push queries finish? or any of you guys have any other solution?
One thing to note:
Above mentions steps executes successfully when I run it 2nd time again in same session of application, it is strange that it don't run in first attempt.

If I have understood your problem correctly, then you might have to change your push logic a bit.
Its always handy to save data in a specific node in Firebase in a single push command. As far as I can see, you're trying to push the data Employees node in two steps. Is it really necessary? You can easily push the empName and the childCompany at once using a single push.
And in your ChildController, you need to add a listener to that node from where you're trying to fetch the data using ref.on. So that you get a callback after a successful store of the data in your Firebase database.
var companyRef=firebase.database().ref("Company/"+Object.keys($scope.emp.Company)[0]);
companyRef.on("value", function(data) {
// This will be triggered once there's a
// change in data in the node the reference is referring to
doSomething();
});
Update
then how I can use set(true) within push?
Take a single object containing both empName and childCompany. Then just use push like this.
// Get the firebase reference of your node
var ref = firebase.database().ref("Employees");
// Create an object first.
var employee = {
empName: "Peter",
company: "CompanyTH"
};
// Pass the object that you've prepared earlier here.
ref.push.set(employee);
This is just an example. You can have nested object. The idea is to passing the whole object at once and add a callback on successful save in Firebase. You might think of something like this too.
ref.set(employee, function(error) {
if (error) {
doSomethingOnError();
} else {
doSomethingOnDataSavedSuccessfully();
}
});
You can try building nested classes like this
var employee = {
empName: "Peter",
Company: {
companyName: "Name",
uniqueID: true
}
};

Related

Meteor with query on publication is not reactive

I have a problem with a meteor publication not being reactive when using a query inside it.
Let's say I have many files, and each file has many projects, so I can go to the route:
http://localhost:3000/file/:file_id/projects
And I would like to both display the projects of the selected file and add new projects to it.
I am currently using angularjs, so the controller would look something like this:
class ProjectsCtrl {
//some setup
constructor($scope, $reactive, $stateParams){
'ngInject'
$reactive(this).attach($scope)
let ctrl = this
//retrieve current file id
ctrl.file_id = Number($stateParams.file)
//get info from DB and save it in a property of the controller
ctrl.subscribe('projects', function(){return [ctrl.file_id]}, function(){
ctrl.projects = Projects.find({file_id: ctrl.file_id}).fetch()
})
//function to add a new project
ctrl.addProject = function(){
if(ctrl.projectName){
Meteor.call('projects.insert', {name: ctrl.projectName, file_id: ctrl.file_id }, function(error, result){
if(error){
console.log(error)
}else{
console.log(result)
}
})
}
}
}
}
The publication looks something like this:
Meteor.publish('projects', function(file_id){
return Projects.find({file_id: file_id})
})
The problem is that, if I insert a new project to the DB the subscription doesn't run again, I mean the array stays the same instead of displaying the new projects I am adding.
I got many problems with this as I thought that meteor would work something like: "Oh there is a new project, let's re run the query and see if the publication change, if it does, let's return the new matching documents"... but no.
I have not found a problem similar to mine as every question regardind querys inside the publication is about how to reactively change the query (the file_id in this case) but that is not the problem here as I don't change the file_id unless I go to another route, and that triggers a new subscription.
My current solution is to expose the complete collection of projects and make the query using minimongo, but I don't know if it is a good workaround (many projects exposed uses too much memory of the browser, minimongo is not as fast as mongo... etc, I don't really know).
Your issue is that the Meteor.subscribe call doesn't know that file_id has changed. There's no reactive relationship between that argument and executing the subscription.
To fix this, whenever you are passing criteria in publish-subscribe, you must write a subscription of Collection inside a tracker.
To know more about trackers, Click here.
While I'm unsure how to do this in Angular, consider this simple Blaze template as an example:
Template.Name.onCreated(function(){
this.autorun(() => {
Meteor.subscribe('projects', file_id);
});
});
Whenever file_id changes, a new subscription is triggered, giving you the desired effect of auto pub-sub utility.
I hope this will give you some insight. It could be easily achieved via Angular JS as well.

How to add to a list in Firebase without overwriting

I am trying to create a 'Favorites' section in my app where you hit a button and it is added to a user favorites list in firebase. I am using the ionic platform.
I created a factory to handle the favourites as they come in. and i use the getAuth() function to get the unique userID so i can just pull it when the user logs on. This is my attempt but i am not getting the result i wanted which is simply something like :
< userid >:
{
0: "fav1"
1: "fav2"
}
.factory('Favourites',function($firebaseArray){
var ref = new Firebase("https://experiencett.firebaseio.com/");
var authData = ref.getAuth();
var favs = $firebaseArray(new Firebase('https://experiencett.firebaseio.com/favourites/'+authData.uid+''));
return {
all: function() {
return favs;
},
add: function(){
var up=new Firebase('https://experiencett.firebaseio.com/favourites/');
var usersref=up.child(authData.uid);
usersref.push({3:"paria"});
},
When you call push() you are generating a unique id. While that is great for many use-cases, it is not good here since you want to control the path that is written.
Since you're already constructing the path with child(authData.uid) you can simply update it with update():
usersref.child(authData.uid).update({3: "paria"});
This will either update the existing value at 3 or write the new value for 3, leaving all other keys under /users/<uid> unmodified.
Alternatively if you want to replace the data that already exists at users/<users>, you can use set() instead of update().
This is all covered in the Firebase JavaScript SDK in the section on storing user data. It is not covered in the AngularFire documentation, since there is nothing specific to Angular about it.

Confusing Firebase Array concept and when how to correctly use it

So I have a controller to load all the tasks for example:
$scope.tasks = taskService.all;
$scope.tasks.$loaded().then(function(data){
console.log (data);
});
//--------Destroy all the AngularFire connection------//
$scope.$on("$destroy", function(){
$scope.tasks.$destroy();
});
The taskService is as below:
var ref = new Firebase(FirebaseUrl);
var tasks = $firebaseArray(ref.child('tasks'));
var uid = Users.LoginUserId();
var Task = {
all: tasks,
getAllTasks: function() {
console.log ("Run get All tasks.");
return $firebaseArray(ref.child('tasks'));
},
... ...
...
Use $scope.tasks = taskService.all; I think I am getting a tasks array - which is ONE firebaseArray. When I logout using Auth.$unauth(), there is no permission errors. When I log back in and go to the same view, $scope.tasks is a empty array = []. I don't know why the taskService don't go get the tasks firebaseArray again. It will only go get the firebase array when I force refresh the browser.
If I use
$scope.tasks = taskService.getAllTasks();
in the taskService - a method / function to return firebaseArray. This tasks will not be empty array after I logout and log back in. But I can tell when I logout, there are many permission errors - look like there are many firebaseArray(s) each time I come to this view (waste of resources since there are multiple same copy of tasks array?!?)
So which is the right way to handle it? Why using a method in services/factory will produce multiple duplicated firebaseAarray when I navigate around my app and back to the same views? Is that really bad? And why using a factory.value way will cause issue when logout and log back in? I can not find any clear explanation about this. Firebase expert please help.
EDIT FOR FINIAL SOLUTION:
Thanks to #nicolas here is my finial solution:
The code provided by nicolas can't work on my solution since this.$$tasks = [] will still be true. It is not NULL. It is just an empty array. So I have to do this instead:
getAllTasks: function() {
if (tasks.length == 0) {
console.log ("Logout and Log back in. Tasks is empty!");
tasks = $firebaseArray(ref.child('tasks'));
} else {
console.log ("Navigate around. Tasks is NOT empty!");
tasks = tasks;
}
return tasks;
},
tasks is already inside a factory and it is private. I don't think I need to use $$.
when you logout you should basically call $destroy on all your angularfire instances, because each of them keep some socket open. So if you have set up some security rules based on your current user id and you log out , you may break them and get exceptions.
Refering to your task service , i would cache the array once instanciated (since firebase will automatically sync it), and listen to child_added events.
var Task = {
getAllTasks: function() {
this.$$tasks = (this.$$tasks || firebaseArray(ref.child('tasks')));
return this.$$tasks;
}

laravel angularjs update multiple rows

i have a sortable table and after successfully moving an item i want to update all the rows in the databasetable which are effected from sorting.
my problem is that i dont know what's the best way to update multiple rows in my database with eloquent and how to send the data correct with angularjs
in angularjs i did this
//creating the array which i want to send to the server
var update = [];
for (min; min <= max; min++){
...
var item = {"id": id, "position": position};
update.push(item);
...
}
//it doesn't work because its now a string ...
var promise = $http.put("/api/album/category/"+update);
//yeah i can read update in my controller in laraval, but i need the fakeid, because without
//i get an error back from laravel...
var promise = $http.put("/api/album/category/fakeid", update);
in laravel i have this, but is there an possibility to update the table with one call instead of looping
//my route
Route::resource('/api/album/category','CategoryController');
//controller
class CategoryController extends BaseController {
public function update()
{
$updates = Input::all();
for($i = 0; $i<count($updates); $i++){
Category::where('id','=', $updates[$i]["id"])
->update(array('position' => $updates[$i]["position"]));
}
}
}
and yes this works but i think there are better ways to solve the put request with the fakeid and the loop in my controller ;)
update
k routing is solved ;) i just added an extra route
//angularjs
var promise = $http.put("/api/album/category/positionUpdate", update);
//laravel
Route::put('/api/album/category/positionUpdate','CategoryController#positionUpdate');
Try post instead put.
var promise = $http.post("/api/album/category/fakeid", update);
PUT vs POST in REST
PUT implies putting a resource - completely replacing whatever is available at the given URL with a different thing. By definition, a PUT is idempotent. Do it as many times as you like, and the result is the same. x=5 is idempotent. You can PUT a resource whether it previously exists, or not (eg, to Create, or to Update)!
POST updates a resource, adds a subsidiary resource, or causes a change. A POST is not idempotent, in the way that x++ is not idempotent.
By this argument, PUT is for creating when you know the URL of the thing you will create. POST can be used to create when you know the URL of the "factory" or manager for the category of things you want to create.
so:
POST /expense-report
or:
PUT /expense-report/10929
I learned via using following
Laravel+Angular+Bootstrap https://github.com/silverbux/laravel-angular-admin
Laravel+Angular+Material https://github.com/jadjoubran/laravel5-angular-material-starter
Hope this help you understand how to utilize bootstrap & angular and speed up your develop by using starter. You will be able to understand how to pass API request to laravel and get callback response.

AngularFire equivalent to $setPristine?

I'm trying to understand the correct workflow to create a $setPristine equivalent with my Firebase data.
My workflow is as follows:
1) Create Firebase object (via the Angularfire Generator 'SyncData')
2) Form data modifies the $firebase object.
3) To update the remote model, I use myRef.$save()
All of that works well. Now, I'm trying to add a "reset" button on the form, so that the locally modified data will revert back to the remotely stored data.
So far, I'm running into problems. I've tried reinitializing the firebase reference eg myRef = syncData('/my/path') but not only does that now work, but it is destroying the remote data object!
What is the correct way to re-pull the remote data to use in my Angular model?
I know this is an old question, but I ran into this issue myself.
After some searching around I found this post: http://grokbase.com/t/gg/firebase-angular/1499haaq4j/editing-data-as-a-copy
Which led me to an outdated code snippet (2 months lol XD) from #Kato: https://gist.github.com/katowulf/8eaa39eab05a4d975cd9
I modified this to work with Firebase 2.3.1 and AngularFire 1.1.3:
app.factory('ResetFactory', function($firebaseArray) {
return $firebaseArray.$extend({
reset: function(itemOrIndex) {
var key, self;
self = this;
key = self.$keyAt(itemOrIndex);
self.$ref().child(key).once('value', function(snap) {
self.$$updated(snap);
});
}
});
});
Which can be called via:
var comments = new RevertFactory(ref.child('comments'));
# variable comment is for example an ng-repeat that's being edited
comments.reset(comment);

Resources