Firebase $remove() object from a nested array? - arrays

Firebase $remove() object from a nested array?
I can't get the Firebase $remove() method to work with nested arrays of objects. I'm making a movies database with an array of movie objects. Removing a movie from the array of movies is working:
$scope.deleteMovie = function() {
$scope.movie.$remove().then(function() {
console.log("Movie deleted.");
}, function(error) {
console.log(error);
});
};
That uses the $firebaseObject $remove() method which just hooks onto an object and removes it. The movie was identified in the array by its key, passing movie.$id into the URL and then grabbing the key from the URL to identify the movie and putting it in the $scope:
$scope.movie = $firebaseObject(ref.child($routeParams.id));
Users can add comments to movies. Each comment is an object in an array of comments. The array of comments is a property of the movie object, so it's a second-level nested array of objects. It seems like a better idea to use the $firebaseArray method $remove(recordOrIndex). I'll pass the comment through from the view:
$scope.deleteComment = function(comment) {
console.log(comment);
};
It passes through the comment:
Object {commentAuthor: "Eva Braun", commentDate: 1462461704268, commentText: "What's not to like?"}
Now I'll use $remove(comment) to delete the comment from the array:
$scope.deleteComment = function(comment) {
$scope.movie.movieComments.$remove(comment).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
The error message is "TypeError: $scope.movie.movieComments.$remove is not a function".
So $scope.movie.$remove() is a function but $scope.movie.movieComments.$remove is not a function.
I checked if this is an asynchronous problem but that doesn't seem to be the issue.
Firebase doesn't like dot notation for nested arrays of objects. I'll use the child() notation:
$scope.deleteComment = function(comment) {
$firebaseArray(ref.child($routeParams.id).child('movieComments').remove(comment)).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
The error message is "Error: Firebase.remove failed: first argument must be a valid function." The documentation says that the first argument must be either a record or an index. The comment passes through without its key, maybe that's the problem?
Let's try using an index instead of a record:
$scope.deleteComment = function(comment) {
$firebaseArray(ref.child($routeParams.id).child('movieComments').remove(0)).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
Same error message: "Error: Firebase.remove failed: first argument must be a valid function."
Let's try removing the comment from the view:
<div ng-repeat="comment in movie.movieComments">
<form>
<button ng-click="movie.movieComments.$remove(comment)">x</button>
</form>
</div>
That does nothing.
Let's try the $firebaseObject version of $remove():
$scope.deleteComment = function() {
$firebaseObject(ref.child($routeParams.id).child('movieComments').remove()).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
That works great! Except that it removes the entire array of comments, when I only want to remove one comment from the array. Let's add another .child():
$scope.deleteComment = function(comment) {
$firebaseObject(ref.child($routeParams.id).child('movieComments').child(comment).remove()).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
The error message is "Firebase.child failed: First argument was an invalid path: "undefined". Paths must be non-empty strings and can't contain ".", "#", "$", "[", or "]"".
I'm not identifying the comment correctly. How about using the key:
$scope.deleteComment = function(comment) {
console.log(comment);
$firebaseObject(ref.child($routeParams.id).child('movieComments').child(comment.$id).remove()).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log("Error, movie not deleted.");
console.log(error);
});
};
Same error message. The key isn't in the comment so the key isn't being passed through when the comment is passed through. I'll pass through the movie object and console log the movieComments array. The array has one object:
Object {-KH0VeEfwIgT1UOUrFzo: Object}
-KH0VeEfwIgT1UOUrFzo: Object
commentAuthor: "Eva Braun"
commentDate: 1462461704268
commentText: "What's not to like?"
Now I'll use $keyAt(recordOrIndex) to get the key:
$scope.deleteComment = function(movie, comment) {
console.log(movie.movieComments.$keyAt(comment));
};
"TypeError: movie.movieComments.$keyAt is not a function". Let's try .child() notation:
$scope.deleteComment = function(movie, comment) {
console.log(movie.child('movieComments').$keyAt(comment));
};
"TypeError: movie.child is not a function". Let's try that with an index number:
$scope.deleteComment = function(movie, comment) {
console.log(movie.child('movieComments').$keyAt(0));
};
"TypeError: movie.child is not a function". OK, $keyAt(recordOrIndex) and $remove(recordOrIndex) are both not working. That suggests there's something wrong with my record or index.
Any suggestions how to remove any object from a nested array in Firebase?

I am not certain with out seeing all of your code, but I have a feeling you are passing in an object with the final .child(comment) when firebase will only accept a string. See this line:
$firebaseObject(ref.child($routeParams.id).child('movieComments').child(comment).remove()).then(function() {
If you found the id or key string representation of what I assume is a comment object you could pass it into the remove function. That is why you are getting the error.
Also you can chain your children like
.child(`${$routeParams.id}/movieComments/comment`)
to keep everything a little cleaner

I figured it out. Firebase doesn't do dot notation so this doesn't work:
ng-click="movie.movieComments.$remove(comment)"
I set up an array of comments in the controller:
$scope.comments = $firebaseArray(ref.child($routeParams.id).child('movieComments'));
Then in the view I set up:
<div ng-repeat="comment in comments">
<button ng-click="comments.$remove(comment)"></button>
</div>
Works perfectly!
There's a mistake in the Firebase documentation. In the "Synchronized Objects" tutorial it gives this example:
{
"profiles": {
"physicsmarie": {
name: "Marie Curie",
dob: "November 7, 1867"
}
}
}
That's a JavaScript object. A Firebase object looks like this:
{
$id: "-KGSpDSJEtvCHetr5679",
{
"profiles": {
$id: "-KGSpDSJ43er5t3udghoe",
{
"physicsmarie": {
$id: "-KGSpDSJEtvCHNeXWriS",
{
name: "Marie Curie",
dob: "November 7, 1867"
}
}
}
}
}
}
This is why you can't use dot notation with Firebase. Firebase objects are nested double objects, with the key as the first property of the outer object and the inner object as the second property. You have to use .child() notation.

Related

How to iterate an object retrieved by Fetch API in VueJS?

I have a data Object resulting by Fetch:
data () {
return {
show: true,
interval: {},
dadosCliente: {},
radio: ''
}
fetchNome() {
fetch('http://localhost:3000/pessoas/'+this.$store.state.cpf)
.then(response => response.json())
.then(data => {
this.dadosCliente = data
this.show = false;
});
}
Output dadosCliente:
[{'nome':'Paulo'},{'nome':'Marcos'},{'nome':'Victor'}]
When I try to iterate v-for in v-radio (vuetify) I receive message saying which item is not declared.
<v-radio-group v-model="radio" mandatory>
<v-radio :v-for="item in dadosCliente" :key="item.nome" :label="`${item.nome}`"></v-radio>
</v-radio-group>
Speding a time reading some articles, posts, but I couldnt finish it. Anyone can help me ?
You shouldn't use binding sign : with directives like v-if or v-for it should be like:
v-for="item in dadosCliente"
Removing the : will make it work...
To add on ..since you are iterating an array, instead of binding the nome to the key, you can do something like this
<v-radio-group v-model="radio" mandatory>
<v-radio :v-for="(item,index) in dadosCliente" :key="index" :label="`${item.nome}`"></v-radio>
</v-radio-group>
In this way, you can even handle objects with redundant nome values.

Return Array from Firebase snapshot method

I'm connecting to Firebase and retrieving data from a given location, what I'm trying to do is loop through the snapshot.val() build and Array, return the Array and then loop through it on the component.html and build a Dropdown.
At present I'm having difficulty figuring out the syntax for such service method, this is my current code. - This is called from app.component.ts within ngOnInit()
getExerciseOptions(): Array<Exercise> {
const items: Exercise[] = [];
this.exerciseDbRef2.on("value", (snapshot) => {
snapshot.forEach((snap) => {
items.push({
id: snap.key,
description: snap.val().description
});
return false;
});
});
}
So this.exerciseDbRef2 points to the table within Firebase as shown here:
private exerciseDbRef2 = this.fire.database.ref('/exercise_description');
The error I'm currently receiving is
A function whose declared type is neither 'void' nor 'any' must return a value.
Which I understand, so when I change return false to return items the new error is:
Argument of type '(snap: DataSnapshot) => Exercise[]' is not assignable to parameter of type '(a: DataSnapshot) => boolean'.
Type 'Exercise[]' is not assignable to type 'boolean'.
I have looked at using child_added but from my understanding this will be called every time a new child has been added into that location, which is not what I'm looking for. The children in this location will not change nor will any be added. - Maybe I've misinterpreted 'child_added' ?
I'm rather new to Firebase so I'm at the beginning on the learning curve so please bare with, I would also like to mention if the way I'm currently doing this is not correct then please bring it to my attention.
So for clarification : Connect to Firebase, retrieve all children from given location i.e exercise_description table, loop through the snapshot, build an Array and return it.
Then on the component loop through the Array and build the Dropdown.
Can someone please explain how I go about returning the Array based off the snapshot.val() ?
You cannot return an array from getExerciseOptions, as the value event is asynchronous.
However, you could return a promise:
getExerciseOptions(): Promise<Array<Exercise>> {
return this.exerciseDbRef2
.once("value")
.then(snapshot => {
const exercises: Exercise[] = [];
snapshot.forEach(snap => {
exercises.push({
id: snap.key,
description: snap.val().description
});
return false;
});
return exercises;
});
}
You would then call it like this:
getExerciseOptions().then(exercises => console.log(exercises));
If you are unfamiliar with promises, you might want to read JavaScript Promises: an Introduction.

Polymer 1.0 observers - not working on array

I set an observer on to catch all polymer recognized events on an property that is an array, but I catch get it to catch the change. In my example below my observer function "bigup" only gets called on when the property, "bigs" is first initialzed.
<dom-module id="parent-page">
<template>
<paper-button on-click="updateAll">Update</paper-button>
</template>
<script>
var temp=[];
temp.push({'conversation':[{'message':'hello'}]});
Polymer({
is: 'parent-page',
properties: {
bigs: {
type: Array,
value: temp
}
},
observers: [
'bigup(bigs.*)'
],
updateAll: function(){
this.bigs.push({'conversation':[{'message':'hola'}]});
console.dir(this.bigs);
},
bigup: function(){
console.log('big Up');
}
});
</script>
I also tried to use bigs.push in the observer but had no success. One part I don't understand is that if I add the following line to my "updateAll" function, the observer catches the change and fires "bigup".
this.bigs=[];
For me this article was helpful as well, it covers both the way to register the observer as well as the splices methods as suggested by Justin XL.
Registering the observer:
properties: {
users: {
type: Array,
value: function() {
return [];
}
}
},
observers: [
'usersAddedOrRemoved(users.splices)'
],
Calling splices methods the Polymer 1.0 way:
this.push('users', 'TestUser');
https://www.polymer-project.org/1.0/docs/devguide/properties.html#array-observation
FYI - this will NOT work in all cases (my initial idea)
When you register the observer in the property declaration like this:
properties: {
users: {
type: Array,
value: function() {
return [];
},
observer: 'usersAddedOrRemoved'
}
},
In this case the usersAddedOrRemoved method is only called when you assign a new array to the users object. It will not however fire when you mutate the array by pushing, popping, splicing etc.
You need to use the push method from Polymer instead of doing this.bigs.push.
So replace that line of code with
this.push('bigs', {'conversation':[{'message':'hola'}]});
For more info have a look at this link.

Get the value from array on the basis of another property array in AngularJS

My Arrays are:
var FirstArr=[
{
"id":"123",
"aboutUS":"Demo About Us"
},
{
"id":"234",
"tutorial":"Demo Tutorial"
}
];
var SecondArr=[
{
data:"aboutUS"
},
{
"data":"toturial"
}
];
I am not getting the value in temp,sTemp as a property is not working and showing undefined.
var sTemp=SecondArr[0].data;
var temp=FirstArr[0].sTemp;
Please Suggest me the solution....
The error is in your notation you should use [] with a variable property name so
var temp=FirstArr[0][sTemp];
See this plnkr: http://embed.plnkr.co/QY9aWDgxll3PcaO6ZgjN/preview
It console logs the temp value.

React error message missing

I have a very simple React case where I have an error in the render function and nothing gets logged when the state gets updated to contain a bad value.
The error is that after the state is updated to contain a 'stuff' value that is not an array, which means that calling join(", ") on the 'stuff' value should fail, but nothing happens. React only keeps the data previously set in the getInitialState-function.
Am I doing something wrong here to loose errors messages or is this just how React works?
var someComp = React.createClass({
getInitialState: function () {
return {persons: [
{name: "Jane", stuff: ["a", "b"]},
{name: "Jim", stuff: ["c", "d"]}
]}
},
componentDidMount: function () {
superagent.get('/api/tags')
.end(function (res) {
// The res.body contains a stuff-value that is not an array.
this.setState({persons: res.body});
}.bind(this));
},
render: function () {
return R.table({}, [
R.thead({},
R.tr({}, [
R.th({}, "Name"),
R.th({}, "List of stuff")
])),
R.tbody({},
this.state.persons.map(function (person) {
return R.tr({}, [
R.td({}, person.name),
// If person.stuff is not an array, then no error is logged..!
R.td({}, person.stuff.join(", "))]);
}))
])
}
});
In my experience with React, it logs much more useful error messages if you build things step-by-step. Do that R.td({}, person.stuff.join(", "))] before the return statement, then use that result later on. If person.stuff isn't an array you'll get a better error message then.
More to the point, why do you want React to throw an error? If it's for development purposes, there are easier ways to check than waiting for error messages from React. Otherwise, shouldn't you be including a check to see if person.stuff is an array before attempting to use it as one?

Resources