Code Monkey home page Code Monkey logo

Comments (22)

jeffbcross avatar jeffbcross commented on May 22, 2024 1

Another thing I forgot to include in the proposal was how to manage pagination, or making the query more interactive. I want to make it possible to incorporate scalar or observable values into the query part of the list. And when new values would be pushed to an observable, the query would be re-performed underneath.

Since the decorator definition object doesn't have access to the class instance, it wouldn't be possible to include direct references to class properties in the annotation. So there needs to be a way to reference the property by name instead of reference. The two ideas that come to mind are a string-based DSL to specify interpolated properties, i.e. query: [['limitToFirst', '{{pageSize}}']] and a more explicit approach using functions to designate dynamic values: query: [['limitToFirst', observableQuery('pageSize')]]. I lean toward the 2nd, explicit option.

from angularfire.

davideast avatar davideast commented on May 22, 2024

These examples looks awesome, Jeff!

I think exposing collections as an Observable will fit well into Angular 2 architecture, and will solve a lot of problems we had in AngularFire 1.

The only other thing I think is needed here is guidance on injecting Firebase references into components.

Just a little question. Is af_unwrap calling .val() on the DataSnapshot?

from angularfire.

jeffbcross avatar jeffbcross commented on May 22, 2024

The only other thing I think is needed here is guidance on injecting Firebase references into components.

Here's the unit test for how I have it implemented locally:
(TL;DR: inject DEFAULT_FIREBASE to get the url, or DEFAULT_FIREBASE_REF to get the ref for the default url).

import {Injector, provide} from 'angular2/core';
import {FIREBASE_PROVIDERS, DEFAULT_FIREBASE, DEFAULT_FIREBASE_REF} from './angularfire';

describe('angularfire', () => {
  describe('DEFAULT_FIREBASE_REF', () => {
    it('should provide a FirebaseRef for the DEFAULT_FIREBASE_REF binding', () => {
      var injector = Injector.resolveAndCreate([
        provide(DEFAULT_FIREBASE, {
          useValue: 'https://ng2-forum-demo.firebaseio.com'
        }),
        FIREBASE_PROVIDERS
      ]);
      expect(typeof injector.get(DEFAULT_FIREBASE_REF).on).toBe('function');
    })
  });
});

Just a little question. Is af_unwrap calling .val() on the DataSnapshot?

Yeah, it'd essentially be (naive cased to support array. should also support objects):

class AfUnwrap {
  transform(input:Observable<any[]>):Observable<any[]> {
    return input.map(arr => arr.map(snap => snap.val()));
  }
}

CC @davideast

from angularfire.

jeffbcross avatar jeffbcross commented on May 22, 2024

I pushed my branch in all its WIP glory: https://github.com/jeffbcross/indy/tree/experiments

from angularfire.

TylerEich avatar TylerEich commented on May 22, 2024

What's the advantage of using a @FirebaseList or @FirebaseObject decorator over a service e.g. new Firebase( pathAndOptions ) or getFirebase( pathAndOptions )?

I noticed that Falcor makes extensive use of Observables, but not by subclassing. They use a Model constructor with methods (getValue, setValue) that return Observables.

Finally, I'm really excited to see things starting to take shape for Firebase and Angular 2. Looking forward to the future of this project 😎

from angularfire.

jeffbcross avatar jeffbcross commented on May 22, 2024

What's the advantage of using a @FirebaseList or @FirebaseObject decorator over a service e.g. new Firebase( pathAndOptions ) or getFirebase( pathAndOptions )?

@TylerEich good question. To answer the "why a decorator" part of the question, using a decorator makes code more statically-analyzable for tooling to be built in the future (tooling for refactoring, stubbing out components with Firebase integration, etc).

I noticed that Falcor makes extensive use of Observables, but not by subclassing. They use a Model constructor with methods (getValue, setValue) that return Observables.

I wanted a succinct, flat-as-possible API. And @Blesh, maintainer of RxJS 5, has officially told me that he approves of subclassing Observable in this way.

Finally, I'm really excited to see things starting to take shape for Firebase and Angular 2. Looking forward to the future of this project 😎

Me, too!

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

I wanted a succinct, flat-as-possible API. And @Blesh, maintainer of RxJS 5, has officially told me that he approves of subclassing Observable in this way.

Just remember that 50% of the reason Observable is a class is so you can put operators on it. The meat of it is really just a function. The other 50% of the reason is so you can derive Subject from Observable.

What I wouldn't do: Subclass Observable and start building a highly stateful type out of it with a bunch of methods that affect that state. If you're doing that, you might want to subclass Subject, depending on what your goals are.

For example:

interface FireNotification<T> {
  type: string,
  data: T
}

class FireSubject<T> extends Subject<FireNotification<T>> { 
  next: (notification: FireNotification<T>) => void;
  error: (err: any) => void;
  complete: () => void;

  subscribe(observerOrNext?: (notification: FireNotification<T>) => void | Observer<FireNotification<T>>,
    error?: (err: any) => void,
    complete?: () => void): Subscription;
}

This would enable you to send like { type: 'delete', data: someRecord } or recieve { type: 'update', data: { whatever: 'here' } }. And do so in a composable way.

from angularfire.

jeffbcross avatar jeffbcross commented on May 22, 2024

Achievement unlocked: a comment from @Blesh .

What I wouldn't do: Subclass Observable and start building a highly stateful type out of it with a bunch of methods that affect that state.

In this case, the mutation methods are just proxying mutations to the Firebase ref, which persists the change and then emits an event such as child_added once it has satisfactorily persisted the change. So the Observable itself isn't tracking state, other than an array that is internally kept in sync as child_moved, child_added, and child_removed events are emitted from Firebase.

from angularfire.

jeffbcross avatar jeffbcross commented on May 22, 2024

Update: I've updated angular/angular issue #6643 to describe how @tbosch recommends we approach this with Dependency Injection instead of a decorator, giving us the same benefits without hacks/side-effects. There's one catch: DI can't do what I need right now, and it may not be easy to convince others on the team that it should.

from angularfire.

robwormald avatar robwormald commented on May 22, 2024

@Blesh's idea is roughly how I've implemented redux-in-rx-for-ng2 and it works pretty well so far.

in effect, its basically something like:

actions$.scan(firebaseReducer, []).subscribe(arr => { ... });

where actions$ is a stream of events like { type: 'update', payload: data }

and firebaseReducer is a function that looks something like below and is responsible for keeping the internal array in sync with the remote

const firebaseReducer = (list = [], action) => {
  switch(action.type){
    case 'update':
      return list.map(item => item.id === action.payload.id ? Object.assign({}, item, action.payload) : item);
    case 'delete':
      return list.filter(item => item.id !== action.payload.id);
    //etc
    default:
      return list;
  }
}

This is wrapped up into a Store (which is roughly what I think a FirebaseRef would become):

class FirebaseRef {
  constructor(url: string);
  next(action): void;
}

which I reckon for ease of use might be extended as (where push() just proxies to next() and appropriately wraps in an Action)

class FirebaseRef {
  constructor(url: string);
  next(action): void;
  push(data):void;
  delete(data):void;
  //etc
}

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

@robwormald this is off-topic, but sort of feel like switch statements in reducers are an anti-pattern. I'd rather see a stream of actions be scanned into state. I'm sure this has been thought of by @gaearon, but just to put in out there:

some pseudocode:

// assume all Action types below implement this:
interface Action<S> {
  act (state: S): S
}

const updates = updateButtonClicks.map((id) => new UpdateAction(id, getUpdatesFromForm()));
const deletes = deleteButtonClicks.map((id) => new DeleteAction(id));

const actions = Observable.merge(updates, deletes);

const states = actions.scan((state, action) => action.act(state), initialState);

(obviously the "classness" of UpdateAction and DeleteAction are totally optional)

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

... after that, it becomes a matter of adding Action types by convention, rather than updating a switch statement, which is more "configuration" than "convention".

from angularfire.

gaearon avatar gaearon commented on May 22, 2024

We use plain object actions in Redux so they are easy to record, serialize, and replay. Classes kill that because nobody will bother to implement deserialize() or something like this. More on Redux “boilerplate” design decisions and how they are driven by use cases we care about: http://redux.js.org/docs/recipes/ReducingBoilerplate.html

Actions are plain objects describing what happened in the app, and serve as the sole way to describe an intention to mutate the data. It’s important that actions being objects you have to dispatch is not boilerplate, but one of the fundamental design choices of Redux.

There are frameworks claiming to be similar to Flux, but without a concept of action objects. In terms of being predictable, this is a step backwards from Flux or Redux. If there are no serializable plain object actions, it is impossible to record and replay user sessions, or to implement hot reloading with time travel. If you’d rather modify data directly, you don’t need Redux.

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

:) I wasn't at all concerned with boilerplate and I'm not married to classes, either.

Replay-ability is an interesting concern, and I see what you mean. Seems like there should be a solution that caters both to replay an maintainability, as a switch statement could get out of hand and large very quickly on some large apps. Perhaps the plain object could be used to look up particular reducer function by convention?

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

Either way, I'm horribly off-topic for this thread. :)

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

I'll take this off-line.

from angularfire.

gaearon avatar gaearon commented on May 22, 2024

Sorry for hijacking the thread, this is my last comment here :-)

as a switch statement could get out of hand and large very quickly on some large apps

This is not true in my experience. You're not actually supposed to write a single reducer function that manages your whole app. You split it into many reducer functions that manage parts of the app's state. Effectively most reducers manage about 5 different actions, and they are composed into other reducers, and so on, up to the root reducer. A giant switch is an anti-pattern, just like a giant component. You're supposed to split them.

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

(meta: @gaearon showed me what he means and he's right) (but I still like a more functional approach than switch statements I think) (maybe) (I dunno)

from angularfire.

jeffbcross avatar jeffbcross commented on May 22, 2024

Thx for putting @Blesh in his place, @gaearon. I've tried and failed many times.

from angularfire.

benlesh avatar benlesh commented on May 22, 2024

TIL: I have a place.

from angularfire.

katowulf avatar katowulf commented on May 22, 2024
@FirebaseList({
  path: 'https://<FIREBASE>/posts',
  query: [['orderByChild', 'timestamp'], ['limitToFirst', 2]]
})

Note that this removes a lot of the flexibility of the current AngularFire design, which allows it to work with complimentary tools like Scroll, NormalizedCollection, MockFirebase, and others. The purpose of these libs is unclear in the future of Firebase, but the point is that such things will exist, and they plug instantly into AngularFire's current design.

It's probably fine to extend FirebaseList in some manner to add in these items, but worth keeping in mind the difficulty incurred vs the benefits of passing a string and query parameters instead of just a Firebase ref.

One idea to ponder would be allowing both. E.g. pass either String path or Firebase ref.

from angularfire.

jeffbcross avatar jeffbcross commented on May 22, 2024

Closing in favor of #39

from angularfire.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.