I'm building an Ionic app with Angular that uses Firebase as the auth / database.
On my app, the user can create different things, or objects. I then take those objects and put them into an array. So I end up with an array of objects. The user can delete things in that local list, changes things, or add new things.
What I want is to be able to fetch the object (that I successfully store) in Firebase, and set it back as the object. I'm new to Firebase and it's api, so I'm a little stuck. Here's my code to retrieve the object from Firebase:
fetchList() {
const user = this.authService.getActiveUser();
if (user != null) {
return firebase.database().ref(user.uid + '/lists').once('value')
.then( snapshot => {
return snapshot.val();
})
.catch( err => {
return err;
});
}
}
I've read all over the Firebase docs and I cant figure out what snapshot.val() returns. Does it return an object or what. If it does, will I have to parse through the object to rebuild my local array of objects?
This is an example structure of some data in Firebase. 0 is the array index. I basically just pass in my array of objects and Firebase stores it like this. Now I want to retrieve it and set it equal to my local array of objects, so the user can have
+ Sidfnh450dfFHdh989490hdfh
- DFjghd904hgGHdhfs9845hfh0
- lists
- 0
hasProperty1: false
isProperty2: false
otherProperty: 'string stuff here'
title: 'List Title'
+ 1
+ 2
Or maybe I'm going about this all wrong. Maybe there exists a magical Firebase method that's similar to two-way data binding, whatever I change on the local object array mirrors Firebase, and vice versa. Any advice appreciated.
Edit
Additionally, something I'm doing locally is reordering the array. My app is a list of some kind, and users can reorder objects in the list. So if I did have a synchronized list with Firebase, and someone reordered some objects in the list, I'm not sure how I would reorder them in Firebase without deleting the entire object-array and writing a new one with the updated array indexes.
If you adjusted the way you stored the data in Firebase you could pull it out in a predefined order.
Notice the additional order key on your list.
+ Sidfnh450dfFHdh989490hdfh
- DFjghd904hgGHdhfs9845hfh0
- lists
- key1
hasProperty1: false
isProperty2: false
otherProperty: 'string stuff here'
title: 'List Title'
order: 1
+ keyn
// ...
order: 2
+ keyz
// ...
order: 3
Within your component you can request that the data comes out in order with a query on the list object.
When saving the new order you can pass the key such as key1, key2 and the new order to the saveOrder function.
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';
#Component({
selector: 'app',
templateUrl: `
<ul *ngFor="let item of list | async">
<li>{{ item | json }}</li>
</ul>
`,
styleUrls: ['./app.scss']
})
export class AppComponent implements OnInit {
public list: FirebaseListObservable<any[]>;
constructor(
private db: AngularFireDatabase
) {}
ngOnInit() {
this.list = this.db.list('/DFjghd904hgGHdhfs9845hfh0/list', { query: { orderByChild: 'order' }});
}
saveOrder(key, newOrder) {
return this.list.update(key, { order: newOrder });
}
}
Related
I'm trying to grab these arrays from one of the nasa open apis -> https://api.nasa.gov/neo/rest/v1/feed?start_date=START_DATE&end_date=END_DATE&api_key=API_KEY
I have a dynamic date in the params so the objects returned match the date, is there any way I can use my date (even though its a string) and turn it into a 'key' value so I can dynamically grab the objects I need?
like => "2021-08-26" would reference { 2021-08-26: [{},{},{}...] }
I have included my code so far with what I've tried, currently I'm trying to just select all the keys inside the {near_earth_objects} using forEach but I'm still getting an error data.near_earth_objects.forEach is not a function
constructor(){
super();
this.state={
asteroids:[],
time: []
}
}
//grab the current time (year-month-day) and store it in the state
componentWillMount(){
var today = new Date();
var start = today.getFullYear()+'-'+0+(today.getMonth()+1)+'-'+today.getDate();
var end = today.getFullYear()+'-'+0+(today.getMonth()+1)+'-'+(today.getDate()+1);
this.setState({time: [start,end]})
}
componentDidMount(){
fetch(`https://api.nasa.gov/neo/rest/v1/feed?start_date=${this.state.time[0]}&end_date=${this.state.time[1]}&api_key=ipAxYzaENbqRKb7GgzFPcH6QUBsHXY3QKB7uXOf5`
)
.then(response => response.json())
.then((data) => {
let asteroids = []
data.near_earth_objects.forEach((arr)=>{
asteroids.push(arr)
})
this.setState({asteroids:asteroids})
});
}
here is an example of the logged data I'm trying to access
It's important to note that the data is coming back as an object where the values are arrays. Since the return data is an object you cannot iterate over it.
However, to get the data you can Object.values(object) (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values) to get an array of the values. This will return something that looks like [ [...], [...] ]
After that you can iterate over this information.
In the end your code should look something like this:
Object.values(data.near_earth_objects).forEach((arr)=>{
asteroids.push(...arr)
})
I have the following JSON definitions:
export class Company {
name: string;
trips : Trip[] = [];
}
export class Trip{
id: number;
name: string;
}
I am able to see the trips in the console using:
console.log(this.company);
In the component I have the following method:
if(this.company) {
Object.keys(this.company.trips).forEach((data) => {
console.log(data);
});
}
What I am getting in the console is the trip's properties names which is "id" and "number".
I would like to know how to access the value.
According to your data structure, you should not even try to do Object.keys what you should be doing as the Trip is an object, is something like following in which you treat the iteration object as a real Trip object
if(this.company && this.company.trips){
this.company.trips.forEach((trip:Trip) => {
console.log(trip.id + '\n' + trip.name);
});
}
if you have any issue with this piece of cod then make sure you are correctly doing your deserialization process and objects are getting cast properly.
I am trying to make $onChanges hook work by using immutable way.
Chat Service
class ChatService {
constructor() {
this.collection = {
1: [
{
chat: 'Hi',
},
{
chat: 'Hello',
},
{
chat: 'How are you?',
},
],
};
}
getCollection() {
return this.collection;
}
getChatById(id) {
return this.collection[id];
}
addChat(id, chat) {
// this.collection[id].push(chat);
this.collection[id] = this.collection[id].concat(chat);
}
}
Chat Component
const Chat = {
bindings: {},
template: `<chat-list chats="$ctrl.chats" add-msg="$ctrl.addMsg(chat)"></chat-list>`,
// template: `<chat-list chats="$ctrl.chats[$ctrl.id]" add-msg="$ctrl.addMsg(chat)"></chat-list>`,
controller: class Chat {
constructor(ChatService) {
this.ChatService = ChatService;
this.id = 1;
// if i get the all the chat collection by
// this.chats = ChatService.getCollection()
// and then use like above in the commented out template,
// and it works and triggers $onChanges
this.chats = ChatService.getChatById(this.id);
}
addMsg(msg) {
this.ChatService.addChat(this.id, { chat: msg });
}
},
};
Chat List Component
const ChatList = {
bindings: {
chats: '<',
addMsg: '&',
},
template: `
<div>
<li ng-repeat="chat in $ctrl.chats">{{chat.chat}}</li>
<form ng-submit="$ctrl.addMsg({chat: chatmodel})">
<input ng-model="chatmodel">
</form>
</div>
`,
controller: class ChatList {
$onChanges(changes) {
console.log(changes);
if (changes.chats && !changes.chats.isFirstChange()) {
// this.chats = changes.chats.currentValue;
}
}
},
};
However, $onChanges hook doesn't fire. I know that in order to make the $onChanges fire, need to break the reference of binding chats in chat-list component from the chat component.
Also I could re-fetch the chats after adding on the addMsg method, it would work and trigger $onChanges but if the msg was from the another user and lets say if I was using Pusher service, it would only update the chats collection on the Chat Service not the chat-list component.
One way $onChanges seems to fire is when I get all the chat collection and then use ctrl.id to get particular chats when passing via the bindings like <chat-list chats="$ctrl.chats[$ctrl.id]" instead of <chat-list chats="$ctrl.chats. However, this will update chat list without doing anything on the $onChanges.
Ideally, I would like to update the chat list on the view by <chat-list chats="$ctrl.chats and then using the currentValue from the $onChanges hook and not use like $watch and $doCheck. I am not sure how to do it. Any help is appreciated. Thanks and in advance.
Here's very basic example of it on the plunkr.
Let's walk trough what your code is doing for a minute to ensure we understand what's going wrong:
The constructor in ChatServices creates a new object in memory (Object A), this object has a property 1 which holds an array in memory (Array 1)
constructor() {
this.collection = {
1: [
{
chat: 'Hi',
},
{
chat: 'Hello',
},
{
chat: 'How are you?',
},
],
};
}
In your component's constructor, you use the ChatService to retrieve Array 1 from memory and store it in the this.chats property from your component
this.chats = ChatService.getChatById(this.id);
So currently, we have two variables pointing to the same array (Array 1) in memory: The chats property on your component and the collection's 1 property in the ChatService.
However, when you add a message to the ChatService, you are using the following:
addChat(id, chat) {
this.collection[id] = this.collection[id].concat(chat);
}
What this is doing is: It updates collection's 1 property to not point towards Array 1, but instead creates a new array by concatenating both the current Array 1 and a new message, store it in memory (Array 2) and assign it to collection[id].
Note: This means the Object A object's 1 property also points to Array 2
Even tho the collection's 1 property has been updated properly when it comes to immutability, the chats property on your component is still pointing towards Array 1 in memory.
There's nothing indicating it should be pointing to Array 2.
Here's a simple example demonstrating what's happening:
const obj = { 1: ['a'] };
function get() {
return obj['1'];
}
function update() {
obj['1'] = obj['1'].concat('b');
}
const result = get();
console.log('result before update', result );
console.log('obj before update', obj['1']);
update();
console.log('result after update', result );
console.log('obj after update', obj['1']);
As you can see in the above snippet, pointing obj['1'] towards a new array doesn't change the array result points to.
This is also why the following is working correctly:
One way $onChanges seems to fire is when I get all the chat collection
and then use ctrl.id to get particular chats when passing via the
bindings like <chat-list chats="$ctrl.chats[$ctrl.id]" instead of
<chat-list chats="$ctrl.chats.
In this case you are storing a reference to Object A. As mentioned above, the 1 property on the ChatService's collection is updated correctly, so this will reflect in your component as it's also using that same Object A.
To resolve this without using the above way (which is, passing Object A to your component), you should ensure the component is aware of the changes made to Object A (as it can not know this when not having access to it).
A typical way these kind of things are done in Angular (I know this is AngularJS, but just pointing out how you can resolve this in a way Angular would do and works fine with Angular JS) is by using RXjs and subscribe to the chats changes in your component.
I'm testing Material Table(mat-table) on Angular 7, here's a weird issue I ran into.
Send a request to jsonplaceholder for fake data in users.service
export class UsersService {
API_BASE = 'https://jsonplaceholder.typicode.com/users';
constructor(private http: HttpClient) {}
getUsers(): Observable<object> {
const url = this.API_BASE;
return this.http.get(url);
}
}
Because jsonplaceholder only returns 10 rows of data, so I concatenate the data for a larger array, say, 30 rows for testing pagination feature with ease. Meanwhile, update the 'id' field with iterate index so the 'id's looks like 1,2,3...30, instead of 1,2,3...10,1,2,3...10,1,2,3...10, which is a result of concatenation, that's it, nothing special.
users.component:
ngOnInit(): void {
this.userService.getUsers().subscribe((users: UserData[]) => {
users = users.concat(users, users);
users.forEach((user, index) => (user.id = index +1));
console.log(users);
this.dataSource.data = users;
});
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
Although the table shows up beautifully, but the 'id's column looks weird, they are not 1,2,3...30 sequentially, instead, they are 21,22,23...30,21,22,23...30,21,22,23...30
I tried to print out the user.id inside the forEach loop, it's all good.
users.forEach((user, index) => {
user.id = index + 1;
console.log(user.id);
});
Where did I go wrong with this? Any clue? Thanks.
P.S, API used in the code: https://jsonplaceholder.typicode.com/users
even though you have 30 array elements after concatenating the array twice, you still only have 10 unique objects. the Object behind users[20] is the same as users[0], so you override the id of the already processed objects from index 10 to 29
you can fix this by creating a copy of each object. There are many ways too do this. a very simple way is serializing and deserializing using JSON.stringify and JSON.parse:
users.forEach(user => users.push(JSON.parse(JSON.stringify(user))));
I've got two types of docs in pouchdb:
todos - list of todos
user - just user number put in pochdb by separate form
When I write todos I also have variable for userNo. This way I know which todos he owns.
I've got two functions in provider to get todos, and user number.
In html list I want to filter todos by user number through pipe:
<ion-item-sliding *ngFor="let todo of todos | filter : 'numer_p' : this.todoService.userNo">
If I enter this number by hand it works great. Todos are filtered by this number.
The problem is that I have two calls in home.ts ionViewLoaded:
//call provider to get all docs to show on the list
this.todoService.getTodos().then((data) => {
this.todos = data;
});
//call provider to get userNo from pouchdb and set variable in the provider
this.todoService.getUser().then((result) => {
console.log("getBadacz result:" + JSON.stringify(result));
this.todoService.userNo = result['name'];
}).then(function(second){;
console.log("second");
});
I need to call getTodos AFTER getUser. So I need to run this functions in sequence using Promises.
Without it this.todoService.userNo in filter is undefined, because it is not set yet. And it will not work.
I tried to do it like this:
this.todoService.getUser().then((result) => {
console.log("getBadacz result:" + JSON.stringify(result));
this.todoService.userNo = result['name'];
}).then(function(second){;
console.log("second");
this.todoService.getTodos().then((data) => {
this.todos = data;
});
});
But there is an error:
EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot read property 'todoService' of null
I tried to arrange this promises in sequence but without success.
Here is fiddle where you can find implementation of functions in provider:
Filter pouchdb docs by userID saved in another doc
Thank you very much for your help.
In situation like this, I would try to include userId in the todo key, that would increase performance (a todos document may have key like this userId_todoId).
So you will not need two promises at all.