Sencha ViewModel Tips


June 30, 2016
Sencha ViewModel Tips

With ExtJS5, Sencha added to the framework a new way of building web apps based on the MVVM pattern. A key role in this pattern is the ViewModel which coordinates the changes between Model data and the View declaratively through data binding. I like the ViewModel concept because it requires less writing, has declarative code, and Views are much cleaner. Based on my experiences, following these tips and thoughts should be very useful for both newcomers and devs familiar with the framework.

  1. Always destroy your bindings in the destroy method of your view. In the example below, the grid is a child of a parent view which has a ViewModel. But if the child is removed from the parent the binding will still be called and can lead to unexpected surprises. So you could destroy them manually, or automatically by adding a ViewModel to the view

    Ext.define('MyApp.view.MyGridTab', {
        extend: 'Ext.grid.Panel',
        xtype: 'mygrid',
        columns: [],
        beforeRender: function () {
            var me = this;
            var vm = this.lookupViewModel();
    
            me.callParent(arguments);
            me.myFieldBinding = vm.bind('{obj.myfield}', me.onMyFieldBindingChange,me);
        },
        onMyFieldBindingChange: function(myfield) {
          // Do some stuff with myfield build the search query, then load the store
          var searchRequest = this.buildSearchRequest(myfield);
          this.searchByRequest(searchRequest)
        },
        searchByRequest: function (searchRequest) {
            var me = this;
            var store = this.getStore();
    
            if (searchRequest) {
                store.getProxy().setExtraParam('search_request', searchRequest);
                store.load();
            }
        },
        destroyViewBindings: function () {
            if (this.myFieldBinding) {
                this.myFieldBinding.destroy();
                this.myFieldBinding = null;
            }
        },
        destroy: function () {
            this.destroyViewBindings();
            this.callParent();
        }
    });
  2. If possible avoid creating bindings in initComponent. The component may not be rendered but instantiated, like in tabpanels’ case, and the binding is called every time a change is detected. This occurs even if it is not needed, which decreases app performance. Depending on the context, you could use the solution below or create your bindings in beforerender.
  3. Only activate bindings when needed. In the above example, the store of the grid tab should load the data only when the tab is visible and/or activated. Currently the ViewModel can’t be disabled and enabled as needed. By default, it is initialized to run in the beforerender phase until destroy time. But like above, sometimes we need to optimize this so the performance is not affected by the bindings. So you may find it useful to have a more generic and flexible solution like the one below. The only way to disable bindings running for now, is to remove them from the ViewModel -> Scheduler by destroying them, which costs less than destroying and creating the ViewModel. For a demo check this fiddle https://fiddle.sencha.com/#fiddle/1bcf and https://github.com/vadimpopa/Ux.mixin.CustomBindable

    Sencha ViewModels bindings

  4. Use explicit binding in formulas to speed up formula parsing
    Ext.define('MyApp.view.MyModel', {
        extend: 'Ext.app.ViewModel',
    
        formulas: {
            // Ok
            isPdfUseDisabled: function (get) {
                var country = get('patentData.country');
    
                if (country) {
                    return !MyApp.util.isPdfCollection(country);
                }
    
                return false;
            }, 
    
            // Much better
            isPdfUseDisabled: {
                bind: '{patentData.country}',
                get: function (country) {
                    if (country) {
                        return !MyApp.util.isPdfCollection(country);
                    }
    
                    return false;
                }
            },
    
            // or multiple binding
            something: {
                bind: {
                    x: '{foo.bar.x}',
                    y: '{bar.foo.thing.zip.y}'
                },
    
                get: function (data) {
                    return data.x + data.y;
                }
            }
        }
    });
  5. Careful with stores data binding (with a memory proxy) because of the nature of store.setData, the grid is not cleared if the bound data is changed to undefined or null
    Ext.define('MyApp.view.MyModel', {
        extend: 'Ext.app.ViewModel',
        formulas: {
            myCollectionsData: {
                bind: '{myData.anObject.myCollections}',
                get: function (myCollections) {
                    return myCollections || [];
                }
            }
        },
        store: {
            myCollections: {
                autoDestroy: true,
                fields: [],
                proxy: {
                    type: 'memory',
                    reader: {
                        type: 'json'
                    }
                },
                // This bind is going to leave your grid un-cleared because of the how
                // store.setData is coded.
                data: '{myData.anObject.myCollections}'
    
                // Much better, if to use a formula to accept default data
                data: '{myCollectionsData}'
            }
        }
    });
  6. As a naming rule, use the onBindingChange pattern when naming your bound handlers. Same goes for the bind references, see myFieldBinding
    me.myFieldBinding = vm.bind('{obj.myfield}', 
    me.onMyFieldBindingChange,me);
  7. Don’t overload your ViewModels with too much code or fragment the code. The balance should be between cleaning up and parent polluting. ViewModels can inherit from each other in a parent – child relation, so you can still access the parent’s data in the child. Also, overloading means more dependencies to track.
    Ext.define('MyApp.view.MyChildModel', {
        extend: 'Ext.app.ViewModel',
    
        alias: 'viewmodel.mychild',
    
        formulas: {
            myCollectionsData: {
                bind: '{myData.anObject.myCollections}',
                get: function (myCollections) {
                    return myCollections || [];
                }
            }
        }
    });
    
    Ext.define('MyApp.view.MyParentModel', {
        extend: 'Ext.app.ViewModel',
        alias: 'viewmodel.myparent',
        data: {
            myData: null
        }
    });
    
    Ext.define('MyApp.view.MyWindow', {
        extend: 'Ext.window.Window',
    
        viewModel: {
            type: 'myparent'
        },
    
        items: [{
            xtype: 'mygrid'
        }]
    });
    
    Ext.define('MyApp.view.MyGrid', {
        extend: 'Ext.grid.Panel',
        xtype: 'mygrid'
        viewModel: {
            type: 'mychild'
        }
    });
  8. Don’t repeat yourself. Sometimes you have to call one property a few times, like with dude in below example. Creating a formula to shorten bindings path won’t add much benefit. Instead it will create additional bindings for the formula itself which means additional overhead for nothing. Here’s a fiddle to experiment with this https://fiddle.sencha.com/#fiddle/1bmk
    viewModel: {
        data: {
            simpsons: {
                dude: {
                    name: 'jon simpson',
                    role: 'developer',
                    phone: '3434343'
                }
            }
        },
    
        formulas: {
            dude: function(get) {
                return get('simpsons.dude');
            } 
        }
    },
    ........
    
        items: [
            {
                xtype: 'component',
                bind: {
                    html: 'hey {dude.name}'
                }
            },
            {
                xtype: 'component',
                bind: {
                    html: 'is your phone {dude.phone} ?'
                }                
            }
        ]
  9. A quick and easy custom Ext.form.field.Display could be done by using a Bind and a Renderer
        xtype: 'displayfield',
        fieldLabel: 'Custom display field',
        bind: {
            value: '{fooArray}'
        },
        customFieldTpl: new Ext.XTemplate(
            '',
                '{name}',
            ''
        ),
        renderer: function (value, field) {
            var html = field.customFieldTpl.apply(value);
            return field.htmlEncode ? Ext.util.Format.htmlEncode(html) : html;
        }

Conclusion

ViewModels are very powerful but you have to be careful and keep the beast under control so you don’t lose performance. Keep in mind that the more bindings you have, the longer it will take to run, so use them wisely. Follow me on GitHub and gist for more tips and extensions.


Popa_Vadim square
Vadim Popa is a web apps engineer and a JavaScript coder. You can find him speaking at Cluj.JS meetups, blogging about Sencha and Angular, and experimenting on GitHub. He likes travelling and mountain biking through the Romanian woods.

  • Don Griffin

    Good stuff! Thanks for the helpful tips and solid advice.

    • http://vadimpopa.com/ Vadim Popa

      Thank you Don, we appreciate your taking the time to look on it.


What We Do

We’ll work closely with your team to instill Lean practices for ideation, strategy, design and delivery — practices that are adaptable to every part of your business.

See what Modus can do for you.

LET'S GET STARTED

We're Hiring!

Join our awesome team of dedicated engineers.

Loading...

APPLY NOW