How to update view in angular, when database has changed? - angularjs

Ive got a SPA using angular and 2 databases in firebase.
So theres a control (with 2 options) where i'd like choose database to get data. For example first option means 1st database, second option means 2nd one.
The question is: "how to update view in angular, when database has changed".
<select class="form-control" ng-model="level" ng-options="lvl in lvls"></select>
<div>{{data}}</div>
in controller
var levelString = "https://amber-inferno-9289.firebaseio.com/" + $scope.level + "/";
$scope.dictionary = new Firebase(levelString);
$scope.dictionary.on('value', function(snapshot){
$scope.data = snapshot.val();
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});

I think what you're looking to do is use the ng-change directive on your select and refactor your dictionary set up logic into a function that is called on select change.
<select class="form-control" ng-model="level" ng-options="lvl for lvl in lvls" ng-change="setUpDictionary(level)"></select>
Then in your controller, create the setUpDictionary function to set $scope.dictionary to a new Firebase pointing at a different url etc.
I think this is what you're looking for based on what I could make from your question.
Hope it helps you out!

Related

Angular Select not updating if model changes

I am working on a website which uses Angular (1.6.4) Select. The content for the select element is loaded via REST if it is requested the first time. But the response of the REST call is cached so that following calls load the data from memory. If I load the website the select element is empty and I can't select any value. If I visit the site again with the data cached, the selectbox allows you to select items from a list. If I redirect the REST call to a file containing the data it works on the first attempt and I can select items as expected
So it seems that the code works in principle but if the model is updated too late the select element does not notice the changes.
Here is the code I am using:
<select ng-model="myData" chosen
class="select--chosen"
ng-change="handleSelection()"
ng-options="myData as myData.value for myData in dataArray">
</select>
The controller code looks like this (called when site is opened):
$scope.dataArray = [];
//$scope.dataArray = [{value : "just a test value"}];
$scope.$watch('dataArray ', function() {console.log("dataArray was changed...")}, true);
getArray();
function getArray() {
DataFactory.getArray().then(function (data) {
$scope.dataArray = data;
});
}
I do get the watch message when I load the site for the first time. When looking for a solution I found several hints but none of them worked for me. This is what I tried:
1) Add
$scope.$apply(function(){ /* code */ });
to set the dataArray inside this function or call it inside of the watch-function. In both cases I got the error on the console that the digest is already updating or so, indicating that it is not neccessary to use scope.$apply
2) Use
$scope.onChange($scope.dataArray);
after setting dataArray = data.
Unfortunately nothing worked. If I uncomment the line:
$scope.dataArray = [{value : "just a test value"}];
I can choose this entry after loading the page and the select view then shows the first entry of the dataArray and afterwards I can access the whole list and select items from it.
So I would like to know what I can do to update the select view after the data is available. Either by adding a Listener or by manually calling the select view to update(), refesh() or so. Is there such a function available?
You can show your select element by some boolean flag, which sets true, when
data loaded.
You can do something like below code.
In controller :
$scope.dataArray = [];
$scope.myData= null;
$scope.isDataLoaded = false; //flag for data loading.
function getArray() {
DataFactory.getArray().then(function (data) {
$scope.isDataLoaded = true; // make true now
$scope.dataArray = data.data; //your result might be data.data
$scope.myData = $scope.dataArray[0]; // you may select 1st as default
});
}
getArray();
In html:
<select ng-if="isDataLoaded" ng-model="myData" ng-class="select-chosen"
ng-options="myData as myData.value for myData in dataArray">
</select>

how to retrieve data nested in two collections from firebase with angular

I'm new in Angular - Firebase development, and I am having problems to understand how to retrieve data nested in two collections.
I have a collection named "Orders", which includes a field call "auth", which is the user ID, and I have another collection that is the "User Profile", wich it's $id is the value of "auth". Inside the User Profile I have a field named roomNumber, and it's content I that I want to retrieve every time I read, in ng-repeat of the Orders.
In my view I was trying to do something like this :
<tr ng-repeat="item in items | filter: searchKeyword ">
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
roomNumber is a function in my controller
$scope.roomNumber = function(id) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
});
return childKey
}
When I run this code and see results in my js console, strange things happend:
1. It repeat a lot of times
2. I can never get the childKey value
I have been reading Firebase documentation, but really I do not understand how to do this "silly" thing, does anybody give me a tip of how to do it?
When you bind a function to the $scope and call it within the html it expects to get an answer back right away when called. So when you query firebase and it takes its sweet time getting you back an answer, angularjs has already gotten an answer of undefined from the function.
So what is happening is that you are registering a callback when you provide the function to rootRef.on and then right after you register the callback you are returning the value of childKey. Unfortunately, childKey only gets set by the callback function (which firebase hasn't executed yet). Therefore angularjs gets an answer of undefined from your roomNumber function.
In order to make this work, you are going to have to get the room numbers beforehand and then probably add them to each of your items in $scope.items then use
<td align="left">{{item.$id}} - {{item.room}}</td></tr>
instead of
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
To load all the room numbers you could call some function like this one after $scope.items has loaded
for (var i = 0; i < $scope.items.length; i++) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + $scope.items[i].$id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.val().room;
$scope.items[i].room = childKey;
});
}
It would change each of the items to have a reference to the room. Unfortunately, that list wouldn't update as the data updates, so the better solution would be to do that same query in whatever function was getting your items from the server and add the room to each item as it was being added to the items list.
To fix the issue with childKey not reading you need to use this:
var childKey = rootSnapshot.val().room;
instead of this:
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
Reference: https://www.firebase.com/docs/web/guide/retrieving-data.html

How to programmatically fill values in successive select in AngularJS?

Programmatically
So I am facing an issue with the select in Angular JS. This is a follow-up question to my previous question
Now I am able to load my values in the second select but I am unable to write the same value to it when the query from DB comes back. So If I select Ford in one select and Figo in another. and press save. The values go into DB correctly but when I come again to that view. Shouldn't that value persist right? I mean I should be able to see figo in the second select. But I am unable to. I tried setting to the ng-model but that method doesn't work. I have also attached a fiddle with the proper comments as to what is not working.
The code uses one of the marked answers from the question.
HTML
<select ng-model="carBrand" name="carBrand" required ng-options=" brand for brand in brands" ng-change="selectedCar(carBrand)"></select>
<select ng-model="carModel" name="carModel" required ng-options="model.name for model in cars[carIndex]"></select>
JS
$scope.brands = ['Ford', 'Honda', 'Hyundai', 'Mahindra',
'Maruti Suzuki', 'Nissan', 'Renault', 'Skoda', 'Tata', 'Toyota', 'Volksvagen'
];
$scope.cars[0] = $scope.cars[0] = [{
name: "Figo",
capacity: 45
}, {
name: "Ecosport",
capacity: 52
}, {
name: "Fiesta",
capacity: 45
}, {
name: "Endeavour",
capacity: 71
}];
$scope.carBrand = $scope.brands[0];
$scope.cars = [];
$scope.selectedCar = function(brand) {
$scope.carIndex = $scope.brands.indexOf(brand);
};
$scope.carModel = "Anything";
EDIT
I think there is an issue in the understanding of the question. All I want is programmatically(by code) set the value of the second select. That's it.
I have forked your fiddle. Have a look: https://jsfiddle.net/4ynkrj0o/2/
var MockSaveMyData = function(itemsToSave){
// I JUST SAVED ALL MY DATE !! :D
var savedItems = itemsToSave;
savedItems.CreatedAt = new Date();
savedItems.CreatedBy = 'YOU !!';
return {
then: function(callback){
callback(savedItems);
}
};
};
You will have to save and retrieve the values on every page refresh.
Like you said, you have cookies where you save your Car object. In that case, you will have to assign the object on the top firth thing inside the controller.
You will also have to update the cookies after you have run the save function. I will suggest doing all this in the success call back which http calls provide. You can update the cookie and then refresh the page. Or you can just assign the updated values and give a message to the user stating that the save call has been successful.
Again, in my opinion, you should avoid reloading the page. It almost defies the essence of Angular. Where you can do almost everything asynchronously.

How do I delete objects within objects in Angular-Meteor?

NOTE: the following code and demo are extracted from a larger Meteor + Angular project.
I have the following functions to select and delete objects:
DEMO: http://plnkr.co/edit/Qi8nIPEd2aeXOzmVR6By?p=preview
$scope.selectParty = function(party) {
$scope.party = party;
$scope.type = party.type;
$scope.date = party.date;
}
$scope.deletParty = function(party) {
$scope.parties.remove(party);
}
$scope.selectOrganizer = function(organizer) {
$scope.organizer = organizer;
$scope.name = organizer.name;
$scope.title = organizer.title;
}
$scope.deletOrganizer = function(organizer) {
$scope.party.organizers.remove(organizer);
}
The Select action works on both Parties and Organizers as you can see in the demo, displaying the data in the table underneath.
The Delete action doesn't work. Although, let me point out that in my app, the one I have on my machine and currently working on in Meteor, the Delete action works splendidly on Parties, meaning the syntax "$scope.parties.remove(party)" works. But it doesn't work on the plnkr demo for some reason :(
My question is really about the Organizers Delete action, where I'm targeting an object (organizer) inside an array inside the selected object (party)… that one doesn't work. I'm wondering why, and what is the right syntax.
NOTE 2: I'm aware of Angular's splice and index but I can't use them here as I'm not simply working with Angular arrays but with database data in Meteor.
Thanks!
The organizer is a part of the party object and not a collection on it's own. So what you would need to do is remove the party from the object and then save the party object.
Note2 is incorrect. Unless you wrote your question and plunker wrong.

how to access $error from nested forms

I have one main form and another subForm. How I can access $error of the child form but without having to directly reference the subForm name?
I want to be able to display that name is required(for example) instead of just knowing the name of the form.
Here's a demo of my problem: http://plnkr.co/edit/QWZArI1UFPpJdjoK8eVn?p=preview
Ok, I wasn't 100% sure I understood your question, but I think I do, so here's a stab at it:
There are a couple ways to access the errors of a child form, but all seem to need the name of the ngForm.
Assuming this structure:
<form name="parentForm">
<ng-form name="childForm"></ng-form>
</form>
you know that you can access it via $scope.childForm.$error, but less known is that it is also attached to the parent form. You can access it with $scope.parentForm.childForm.$error, but obviously, that's no good, since you still need the name.
You could get hacky and loop through the properties on the parent form and try to tell which one is the child form and go from there.
Lastly, as we've discussed on Twitter/GitHub, I have a directive that kinda does some of this magic for you. It did have a bug that didn't handle embedded forms correctly, but I fixed it. Check out the new version of my directive that tries to simplify handling errors with Angular:
https://github.com/CWSpear/angular-form-errors-directive
I added the ability to display all the errors of all the child ngForms with a flag in v1.3.0.
I think for this particular use case, my simple getErrors method on the scope is a more robust solution than the formErrors directive proposed in the accepted answer. Have a look at this comparison of the two solutions. The formErrors directive will clear errors from the child forms if the bottom child form is filled out. Play around with data entry and you will quickly see other bugs.
The getErrors solution is not pretty, but it is very simple and one can easily see how it might be improved to provide clearer messages.
The JavaScript:
var app = angular.module('app', ['FormErrors']);
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.people = [{name: ''}, {name: ''}];
$scope.allErrors = [];
$scope.getErrors = getErrors;
function getErrors(formObject){
$scope.allErrors = [];
extractErrors(formObject)
}
function extractErrors(formObject, parent) {
for (var e in formObject.$error) {
var path = parent ? parent + "." + e + "." + formObject.$name : formObject.$name + "." + e;
var err = formObject.$error[e];
if (err instanceof Array){ // this is an array of sub-forms
err.forEach(function (subForm, i) {
extractErrors(subForm, path + "[" + i + "]");
});
} else {
if (err === true) {
path = path.replace(new RegExp("." + e, "g"), "");
$scope.allErrors.push(path + ": " + e);
}
}
}
}
}]);

Resources