Skip to content

Instantly share code, notes, and snippets.

@jeffling
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeffling/eb88e988ff7a5226de8f to your computer and use it in GitHub Desktop.
Save jeffling/eb88e988ff7a5226de8f to your computer and use it in GitHub Desktop.
Angular Modelstore

Usage

Using this in controllers

$scope.client = ModelStore.get('Client', options);  // modelstore returns object reference that may or may not contain information,
$scope.client.firstName = 'bar'; // this isn't persisted by the server yet, but lets just be optimistic. this change will be shown in any other controller that's pointing to it.
$scope.client.$update(); // will be updated to server and fetched again to make sure change actually goes through. if it doesn't, emit an error! but the true model is reflected in the scope now.

The resulting object from ModelStore.get('Client', options); will be updated in all scopes (and subsequently, views) that use it, even before any $update.

Declaring a model (coffeescript for now sorry)

class Client
  ###
    Top level variables that aren't one of `methods|relations|name|computed`
    will overridden by your actual model.

    You don't need to define attributes that you will from the mode.
    You can. If it you makes happy.
  ###
  id: null

  ###
    Name
    ====
    Name your model. This will be used to define relations.
  ###
  name: 'Client'

  ###
    Methods
    =======
    Functions are bound to the class, so you can call stuff like @id
    or other actions.

    The name of the function you can call is the name of the
    action, with $ prepended. So 'actions.fetch' would be 'this.$fetch'

    You can pass either a string or a function as the value.

    The string will in the format of "{GET|POST|PUT|DELETE} {URL}".

    The function will take an object, to be sent as JSON in the case of POST|PUT
    or transformed into query parameters to be appended to the url in other cases.

    The string and the function arguments will be used as a key for caching
    the model.
  ###
  methods:
    # fetch action is mandatory. One of the only forced conventions :)
    fetch: "GET /api/v1/client/#{@id}"

    # $data is a convenience variable that doesn't
    # contain your methods or computed properties
    update: ->
      $http
        method: 'PUT'
        url: "/api/v1/client/#{@id}"
        data: @$data

  ###
    Computed Properties
    ===================
    These computed properties are recalculated on every change to the model.
    You can edit them, but your changes will be overridden.

    No symbol will be prepended, so make sure there are no naming conflicts
    with the actual data returned by server. If there are, the computed properties
    take precedence.
  ###
  computed:
    fullName: ->
      @firstName + ' ' + @lastName

  ###
    Relations
    =========
    When this model is updated, these other defined models will update as well.

    The name of the model you want updated is the key the key of the `relations`
     object.

    The value of this hash will be passed as the argument to the target model's
    `fetch()`. If it is a function, the value of the function will be used
  ###

  relations:
    'Address': {id: @addressId} # calls Address.fetch({id: addressId})
    'Bookkeeper': ->
      @assignedBookkeeperId

Why?

Why not just have a singleton that stores data for itself for every model I need? That would totally work and have the same advantages.

  1. Collections: Managing collections would be a pain without a centralized thing, or a collection specific class. What I would like to do is be able to define models that are 'collectionable' without explicitly creating a collection class, which I think is overkill things like arrays exist. Ie. define static methods for searching that return multiple instances of itself and stores in the ModelStore. Allowing for explicitly set computed values and so on for each model would keep each model's code very clean and specific to being 'one object'.

  2. Error handling: Don't handle errors using promises, http interceptors, or specific api-accessing services. We can manage error handling really easily since all fetches will be going through the ModelStore which then calls functions defined in the Model. This keeps our model light and free of non-model related logic like auth errors, sync weirdness, and so on. It also keeps the controller light because it doesn't have to do any fail callbacks for error handling. It can just use events (if you're watching for specific errors) or just stick all errors on to the top of the page because errors will be in an errors object. This will also allow the controllers to access any given model-related errors.

  3. Cascading changes: Having a global service means we can update/get certain models if other models are also being updated/gotten.

Other notes that were on my workflowy:

  • should be configured through model definitions - keep global configuration to the minimum
    • fetch all models from global service
      • keeps object references alike for easy binding
    • when fetching from modelstore
      • queue update from server + return existing data if exists.
      • when modelstore fetches from api, always update, never overwrite existing references.
      • if doesn't exist, return an empty object. The server fetch will catch up.
    • register models into modelstore on run or config step.
      • prefetch if models specify
      • declare relations
      • actions on model
        • define actions in model.
          • provide a base class to wrap actions in useful things for queueing and the like.
        • results in fetch after successful actions
          • some actions return updated object. use that instead
    • cascading changes
      • depending on relations set by models, it will fetch other models from server.
      • never fetch the same model more than once at any fetch event - prevent recursive fetching
    • error handling
      • emit events when fetches fail
      • store errors in a hierarchy so you can show all errors or specific errors
    • why not angular-data
      • too many abstractions - I want to define how my model interacts with my server. http calls are not hard, not all APIs are strict-REST. I don't want to do custom adaptors.
      • does too much
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment