Structuring an ionic 3 Firebase app


#1

Can someone advice me on the best (or at least, a good) approach for an app that gets data from Firebase? My typescript/angular knowledge is a bit fuzzy, so bear with me.

Here’s my current scenario. An Ionic 3 app for buying/selling stuff. I have individual class files, such as …

export class Product
{
    id: number = new Date().getTime();
    name : string;
    // ... and so on
}

a service file (functions.ts) with all the functions (dozens of them) used on the app, one of the functions is like so …

 getProduct(id):Observable<Product>
 {
   return Observable.create(observer=>
   {
     console.log("id passed: ", id);
     firebase.database().ref('/products/'+id).once('value')
       .then((req)=>{observer.next(req.val())})
       .catch((err)=>{console.log(err)})
   });
 }
/// ... and lots more

then in the other pages, I do something like

import { myfunctions } from '../../providers/functions';
...
constructor(private myfuncs: myfunctions .... )
....
var prod;
this.myfuncs.getProduct(5).subscribe((p:Product)=>
{ prod = p;}

I’m not sure this is the best approach. I’m not too fund of the idea of importing the functions file in every page - I’d rather have each class contain its relevant functions (create(), edit(), delete(),…)

Ideally, I’d like to do something like this …

For the class (dunno if the syntax is correct. Please let me know)

export class Product
{
    id: number = new Date().getTime();
    name : string;
    // ... and so on

   constructor(id:number = null)
   {
    firebase.database().ref('products/' + id).once('value')
            .then((req)=>{return req.val();})
            .catch((err)=>{console.log(err)})
   }

  createProduct(newprod:Product)
  {  // add product to firebase  }

  deleteProduct(id:number)
  { // delete from firebase}

  // ...etc
}

then in other pages…


let prod = new Product(1) //returns the product with ID of 1

Is this a good approach, or can someone suggest a better one, or point me in the right direction? Thanks.


#2

This is a bit of a religious topic, so feel free to ignore my opinion and carry on doing things the way you are.

That being said, I believe it is more trouble than it is worth, especially in a language like JavaScript whose OO functionality is all illusory, to try to put intelligence into data containers like your Product. Instead, I make all of those interfaces instead of classes and put all the intelligence into service providers. So all your Firebase interaction I would put into ProductService and make Product look like:

export interface Product {
  id: number;
  name: string;
  // etc
}

One big advantage of this design philosophy is that you don’t have to mess around with blessing objects that come in over the wire from sources like storage or HTTP.


#3

Thanks.
Just curious, why choose interfaces over classes? Any advantages over the other?


#4

Well, I mentioned one: no need to bless things. In a language with true OO support, there would be a mechanism allowing you to serialize and unserialize objects. We don’t have that in JavaScript, which means that the app programmer is responsible for doing it. That makes lovely provider methods like:

allWidgets(): Observable<Widget[]> {
  return this.http.get(api);
}

…turn into twisty mazes of needless new and copy constructors if Widget has to contain a bunch of methods.

It also just fits better into the way I think about things. Chefs cook meals, not ingredients, and so service providers should contain all the logic, letting data just sit in dumb PODOs.

Another problem that I always found myself banging into is that eventually I needed a reference to something from within the “smart” data object that is managed by DI, and at that point you hit a serious wall. Now I have to build some sort of bridge that allows me to inject helper objects into these data objects. Since the service provider either is that helper or can easily inject it, why don’t I just move all that stuff out of the data container and into the provider? That’s what I always ended up doing and so I define data containers as interfaces to help me resist the temptation to put methods into them.