Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR: Allow for class instance to be passed as data #311

Open
Supamiu opened this issue Nov 9, 2017 · 12 comments
Open

FR: Allow for class instance to be passed as data #311

Supamiu opened this issue Nov 9, 2017 · 12 comments

Comments

@Supamiu
Copy link

Supamiu commented Nov 9, 2017

[REQUIRED] Describe your environment

  • Operating System version: Windows 10
  • Firebase SDK version: 4.6.0
  • Firebase Product: Firestore

Describe the problem

When trying to add a document to a collection or to set it, the item has to be of type object, meaning that it can't be an instance of any class.

The error is:

FirebaseError: Function CollectionReference.add() requires its first argument to be of type object, but it was: a custom Item object

Steps to reproduce:

Simply run this in an angular 5.0.0 environment with angularfire2 5.0.0-rc.3:

import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';

export class Item { name: string; getName():string{return this.name;} }

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngFor="let item of items | async">
        {{ item.name }}
      </li>
    </ul>
  `
})
export class AppComponent {
  private itemsCollection: AngularFirestoreCollection<Item>;
  items: Observable<Item[]>;
  constructor(private afs: AngularFirestore) {
    this.itemsCollection = afs.collection<Item>('items');
    this.items = this.itemsCollection.valueChanges();
  }
  addItem(item: Item) {
    this.itemsCollection.add(item);
  }
}

I had to write this to make a temporary fix (on my top-level firestore data model class):

getData(): object {
        const result = {};
        Object.keys(this).map(key => result[key] = this[key]);
        return result;
    }

Then I replaced add(item) by add(<Item>item.getData()), which is kinda ugly but works like a charm.

@BinsGuns
Copy link

Hello !

I found another solution instead of mapping the object. You can use
var data = JSON.parse(JSON.stringify(item));
then collection.add(data);

@s3ppo
Copy link

s3ppo commented Jan 19, 2018

thanks @BinsGuns ,

i tried this with the stringify and normaly it works, but where it not works is when a GeoPoint is inside the object... in this case the firebase.firestore.GeoPoint will be destroyed once it is in the database.

@comfortme
Copy link

@BinsGuns
that works but when you get the data, it's just a json object.
all class functions and methods are gone and re-creating the class from this json object is just a pain in the ass.

@Supamiu
Copy link
Author

Supamiu commented Jan 22, 2018

@comfortme I'm using https://github.com/kaiu-lab/ng-serializer to solve this pain, but still, having to create a clone of your object to be able to pass it for the data is unefficient as it could lead to performance issues with very large objects.

@comfortme
Copy link

@Supamiu
since firebase has no typescript support i dont think this is a firebase issue maybe angularfire should handle it but in the meantime i found this awesome library that can transform classes to objects and vice versa.
perfect for this job
https://github.com/typestack/class-transformer

@andreluisce
Copy link

For my solution I had an Interface:

export interface Launch {
  id: string;
  date: Date;
  value: number;
}

const project= {} as Launch;
this.db.collection<Launch>('launches').add(project);

@AppField
Copy link

I've just found another (pretty obvious) Solution on StackOverflow:
Just use the spread operator

const project = new Project();
this.db.collection<Project>('launches').add({... project});

@siegenthalerroger
Copy link

siegenthalerroger commented Nov 24, 2018

Does anyone know why this is prevented? In the code it is a very clear explicit check they do to see if the object has Object.prototype, so this isn't by accident. All the work arounds (while working) aren't really "nice".

Like to be clear, would a PR be accepted that just removes this check?

@mikelehen
Copy link
Contributor

Yeah, that check was intentionally added for essentially 2 reasons:

  1. So your data roundtrips seamlessly. Right now, whatever you pass to DocumentReference.set() is exactly what you'll get back from DocumentSnapshot.data(). If we allow you to pass in custom objects, we'll lose fidelity, since it'll come back out as a plain object (so your methods will be missing, instanceof checks will fail, etc.).

  2. We'd like to someday support custom objects in a flexible way, similar to our custom object support for Android. This would (somehow) let you write custom objects, and then we could read them back out and re-instantiate your custom class for you.

That said, we were on the fence about being so strict about preventing custom objects, and we could potentially revisit this in the future and loosen the restriction. But I think we're inclined to keep it for now.

@dneprokos
Copy link

var data = JSON.parse(JSON.stringify(item));

Thanks. Simple solution. But it works.

@IdanCo
Copy link

IdanCo commented Jun 6, 2019

bad idea, you'll lose all timestamp type values.

Firestore stores dates as a special type. when you stringify it you're left with seconds and nanoseceonds properties, but without all the methods and also you lose the native db functionality of sorting and filtering.

@Y-AH
Copy link

Y-AH commented Jul 30, 2019

You can use
var data = {...item}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests