Firestore: Add Custom Object to db
Asked Answered
C

10

25

Good Morning,

I tried adding a new created object from this class:

export class Sponsor implements ISponsor {

  title: string;    
  description?: string;
  creation: ICreation;

  constructor(title: string, description: string, author: string) {
     this.title = title;
     this.description = description;
     this.creation = new Creation(author);
  }
}

and in my service, the create function looks like:

createSponsor(sponsor) {
   sponsor.id = this.afs.createId();
   return this.collection.doc(sponsor.id).set(sponsor);
}

When I try it this way, I get the following error:

FirebaseError: [code=invalid-argument]: Function DocumentReference.set() called with invalid data. Data must be an object, but it was: a custom Sponsor object

How can I solve this issue?

Clachan answered 5/10, 2017 at 5:50 Comment(0)
C
33

You can also use Object.assign({}, sponsor)

so in yor case it would be

this.collection.doc(sponsor.id).set(Object.assign({}, sponsor));
Copyright answered 10/10, 2017 at 15:35 Comment(3)
This works, but I found it very "hack-ish". Why Firebase has no correct way of dealing with this?Bantam
This works fine but you should take in care that Object.assign not working for deep clone.Combustion
doesn't suit for nested objectsPlasty
E
24

You could also serialize your object into JSON and deserialize it back into a regular JavaScript object like

this.collection.doc(sponsor.id).set(JSON.parse( JSON.stringify(sponsor)));

works with deep nesting.

Expansionism answered 10/12, 2017 at 11:59 Comment(2)
Have you experienced any limitations using this method? I'd assume it'd be way harded to query data or am I mistaken?Fachanan
this will corrupt the timestamp object when saving to firestore.Pelayo
G
8

Firestore does not support that. But you can use https://github.com/typestack/class-transformer It works perfectly fine for us.

Gerger answered 3/6, 2018 at 15:52 Comment(3)
Which method is recommended?Endocranium
I found using a combination of '.set(classToPlain(yourObject))' works wellPieeyed
Since recently, firebase has a function called withConverter. You can through the classToPlain and plainToClass into it. That ways you code get's clearning. (That's all) firebase.google.com/docs/reference/node/…Caricature
C
6

Thx to Fabian Wiles - I got it!

while firebase could send the data inside your object to the database, when the data comss back it cannot instantiate it back into an instance of your class. Therefore classes are disallowed

just save an object like this:

interface Person{
  name: string;
  age: number
}

var person: Person = { name: 'Toxicable', age: 22} ;
Clachan answered 5/10, 2017 at 7:9 Comment(1)
This is only fine as long as you only use an interface, and not a class constructor.Bantam
S
3

Not sure if I'm missing something here but there seems to be a much simpler solution to this problem now.

Just use a spread operator to "splat" the class instance into an empty object like {...myClass}.

let myClass = new ClassInstance(
  "newClass",
  "This is a new class instance"
);

addDoc(dbReference, { ...myClass }).then(() => {
 console.log("Document successfully written!");
});
Shuler answered 21/11, 2021 at 6:37 Comment(0)
E
2

For my solution I had an Interface:

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

}

const project = {} as Launch;

this.db.collection('launches').add(project);

Ecuador answered 27/1, 2018 at 5:51 Comment(0)
B
1

It's really strange behavior from firebase. And that how I fixed it - by creating new Interface and adding convertation method to my class:

export class Happening {
 constructor(
  public date: EventDate,
  public participants: Array<string>,
  public title: string,
  public text: string,
  public uid?: string,
  public id?: string
 ){}

 public toDto = (): HappeningDto => {
  return {
    date: {
      year: this.date.year,
      month: this.date.month,
      day: this.date.day
    },
    participants: this.participants ? this.participants : [],
    title: this.title,
    text: this.text ? this.text : '',
    uid: this.uid,
    id: this.id ? this.id : null
  }
 }
}

export interface HappeningDto {
 date: {
  year: number,
  month: number,
  day: number
 },
 participants: Array<string>,
 title: string,
 text: string,
 uid?: string,
 id?: string
}

Now, I can do

add(event: Happening){
  event.uid = this.uid;
  this.$afs.collection<HappeningDto>('events').add(event.toDto())
    .then(
      (success) => console.log(success),
      (err) => console.warn(err)
    )
}
Bat answered 24/10, 2017 at 15:22 Comment(0)
E
0

If you use Angular and AngularFire2, you can use AngularFirestype. This module is meant to replace AngularFirestore and allow to get and set data to Firestore directly with custom objects.

To do so, 3 steps are required:

1. Install angular-firestype

`npm install angular-firestype --save`

2. Initialize AngularFirestype module with a mapping object

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AngularFireModule } from 'angularfire2';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFirestypeModule, ModelType } from 'angular-firestype';
import { environment } from '../environments/environment';

import { User } from './user.ts';
import { Address } from './address.ts';
import { Message } from './message.ts';

/**
 * Definition of the app model mapping.
 * For more information, see https://github.com/bricepepin/angular-firestype#mapping-object.
 */
const model: {[key: string]: ModelType<any>} = {
  users: {
    type: User,
    arguments: ['username', 'image'],
    structure: {
      adress: Address
    },
    subcollections: {
      messages: Message
    }
  }
};

@NgModule({
 imports: [
   AngularFireModule.initializeApp(environment.firebase),
   AngularFireAuthModule,
   AngularFirestypeModule.forRoot(model),   // Import module using forRoot() to add mapping information
 ],
 declarations: [ AppComponent ],
 bootstrap: [ AppComponent ]
})
export class AppModule {}

3. Inject AngularFirestype service

import { Component } from '@angular/core';
import { AngularFirestype, Collection, Document } from 'angular-firestype';

import { User } from './user.ts';

@Component({
 selector: 'app-root',
 templateUrl: 'app.component.html',
 styleUrls: ['app.component.css']
})
export class AppComponent {
   const users: Observable<User[]>;
   const user: User;

   constructor(db: AngularFirestype) {
       const usersCollection: Collection<User> = db.collection<User>('users');
       usersCollection.valueChanges().subscribe(users => this.users = users);

       const userDoc: Document<User> = usersCollection.doc('user1');
       userDoc.valueChanges().subscribe(user => this.user = user);
       userDoc.set(this.user);
   }
}

You can basically use AngularFirestype like you use Angularfirestore.
For more details, see the homepage here: https://github.com/bricepepin/angular-firestype.

Emulate answered 4/2, 2018 at 15:21 Comment(0)
N
0

I was having a similar problem using vue and nuxt

firebase.firestore().collection('example')
   .doc()
   .set({
       'foo' : 'boo'
   })

Error:

Data must be an object, but it was: a custom Object object

This link helped me

Nancienancy answered 28/11, 2020 at 13:26 Comment(0)
S
0
export class Sponsor implements ISponsor {
  title: string;    
  description?: string;
  creation: ICreation;

  constructor(title: string, description: string, author: string) {
     this.title = title;
     this.description = description;
     this.creation = new Creation(author);
  }

 toJson(){
   return {
     title:this.title,
     description:this.description,
     creation:this.creation
   }
}

in service, the create function will looks like:

createSponsor(sponsor) {
   sponsor.id = this.afs.createId();
   return this.collection.doc(sponsor.id).set(sponsor.toJson());
}
Sonyasoo answered 26/8, 2021 at 13:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.