Search code examples
javascriptember.jsember-dataember-cliember-components

Creating instances of Ember models not attached to a store


I'm using Ember 2.0 with ember-data 2.0.

In Rails it's very common to model forms and components using real instances of models. For a posts/new form you would pass in a Post.new and use that inside the form.html.erb template.

In Ember that is made difficult because calling new Post creates a broken model. Instead you're encouraged to use stores, and with that use this.store.createRecord('post');.

This is fine, but not when building isolated components. For example a form where the user can add multiple models, say a category creator. In my head the structure would be something as follows:

category-form/template.hbs

<button {{action 'addCategory'}}>Add category</button>
{{#each categories as |category|}}
    {{input value=category.name}}
{{/each}}
<button {{action 'save'}}>Save</button>

Then the component.js would be something like:

category-form/component.js

import Ember from 'ember';
import Category from 'app/category/model';

export default Ember.Component.extend({
    categories: [],

    actions: {
        addCategory() {
            // THIS DOES NOT WORK
            this.get("categories").pushObject(new Category);
        },

        save() {
            this.sendAction("saveCategories", this.get("categories"));
        }
    }
});

The example above does work, but would instead require this.store.createRecord. But as far as I know the component does not have access to the store. This is sane because that would be the component messing with a global state. Also when using createRecord you end up with lots of residual models in the store if the user navigates away without saving the model.

I want the category-form component in this example to be completely isolated from the rest of the global state.

My question is, how would this be handled correctly using ember logic?


Solution

  • All you wrote is correct and it is considered as a good pattern - your components work without store and some parent of them (preferably route, but may be controller) is handling persisting these data to API.

    In your example you do not have to use store in component at all. You can create some Ember.Object instances on each addCategory action execution which will be send to your parent. This parent would obtain the array of Ember.Object, copy the attributes that you want to use to instances of models and save them.

    import Ember from 'ember';
    
    export default Ember.Component.extend({
        categories: [],
    
        actions: {
            addCategory() {
                this.get("categories").pushObject(Ember.Object.create({
                    name: ''
                });
            },
    
            save() {
                this.sendAction("saveCategories", this.get("categories"));
            }
        }
    });
    

    And in your route you can do as follows:

    actions: {
      saveCategories(categories) {
        let categoryRecords = categories.map((item) => {
          return this.store.createRecord('category', { name: item.get('name') });
        });
      }
    }
    

    On the other hand, if you would need some features of Ember Data models as relationships, you can actually send the action addCategory up to the route/controller, create the model and pass down as binding to that component:

    {{category-form categories=categories saveCategories="saveCategories" addCategory="addCategory}}
    

    And then in your route/controller:

       categories: [], 
       actions: {
          addCategory() {
            this.get('categories').pushObject(this.store.createRecord('category'));
          },
          saveCategories() {
            this.get('categories')... // do what you want with them
          }
        }