45

I struggle when building an app in ExtJS 4, and part of that is confusion on when to configure something in initComponent() and when not to...

For example, in Sencha's own MVC Application Architecture doc, when first creating the grid view, they defined the inline store in the initComponent() method. (See "Defining a View" section)

Further down, when they factored out the store into a separate class, they moved the definition outside of initComponent(). There is a helpful comment that draws attention to this fact, but there is no explanation. (See Creating a Model and Store section)

I guess the reason is supposed to be obvious, but I'm missing it. Any pointers?

romacafe
  • 3,098
  • 2
  • 23
  • 27
  • 1
    got some great answers to a fairly inocuous looking question here. Good work! Im just learning javascript and extjs having come from a c# background. It's really useful to see posts that try to get to the bottom of things like this. I also found that article raised as many questions as it answers. Suppose you cant explain everything all in one go though – JonnyRaa Feb 07 '14 at 10:49

4 Answers4

44

If you do not have a deep understanding of how ExtJS class system work, you may want to follow this:

Declare all non-primitive types in initComponent().

Terminology

  • Primitive types - strings, booleans, integers, etc.
  • Non-Primitives - arrays & objects.

Explanation

If the component you extend is to be created more than once, any non-primitive configs declared as a config option (outside initComponent) will be shared between all instances.

Because of this, many people experienced issues when an extended component (typically an extended grid) is created on more than one tab.

This behaviour is explained in sra's answer below and in this Skirtle's Den article. You may also want to read this SO question.

Community
  • 1
  • 1
Izhaki
  • 23,372
  • 9
  • 69
  • 107
  • 4
    Your answer fails to make clear that it's your personal vision that goes against best practices and Sencha recommendation. Also, I would advise you to revisit Skirtle's article as it seems to me that you got it totally wrong. He was actually recommending *against* using initComponent unless it's justified. – Alex Tokarev Jan 24 '13 at 06:30
  • 4
    @AlexanderTokarev I don't see any reason why this is a personal visison to go. Define a Non-Primitive type as a property and all instances will share it (unless you are doing something against it) **But what is missing** is, that a declaration don't need to be done strictly in the `initComponent` it can be done in any method which would then normally called by the `constructor` or the `initComponent` or even any other method. Can you please link me to this recommendation or best practice which goes against this? – sra Jan 24 '13 at 07:33
  • 3
    @AlexanderTokarev, here on SO we get every two weeks or so a question with a non-working/buggy code, the solution for which is to move array/object configs into `initComponent()`. It is important for me to know if I've provided wrong information. Could you please explain better why you think this recommendation should not be given? – Izhaki Jan 24 '13 at 15:05
  • @Izhaki Now that you've mentioned it, I realized that we don't have a guide focusing on this problem; indeed the App Architecture guides advocate wrong approach. I shall take time and write up a piece describing the difference and why declarative approach is better. That would probably take some time though. Thanks for bringing my attention to it! – Alex Tokarev Jan 25 '13 at 18:44
  • @Izhaki Broadly, the problem is that people do not understand what Ext class system is about, and try to fight it instead of going with it. Moving broken code to `initComponent` makes an impression of solving the problem, but in fact it only shifts it somewhere else. As a rule of thumb, you should avoid creating components manually - use subclasses to apply config options and logic you need to stock component classes, and then reuse them again as config options via xtype. I'll try to explain it more in my answer below. – Alex Tokarev Jan 25 '13 at 18:55
  • I must admit that @AlexanderTokarev is right, that your answer it not all in all the best way. But in my experiences it was always hard to explain people why they can add that config but not that reference and so I began to explain it the easy way (for me). I think the point is that people should not create instances within a class configuration. But see my answer on this and feel free to comment – sra Jan 26 '13 at 09:37
  • +1 for the good link. Not sure I agree with the post having read some of the other material but I can see where you are coming from. Having said that it mostly seems to be important to use configs for inheritance so this approach is fine in most places – JonnyRaa Feb 07 '14 at 10:52
  • Ahh this answer is so confusing and gives me the run-around to go visit other blogs or posts. – Horse Voice Oct 13 '15 at 15:37
22

First I will take a stand on my Comments:

@AlexanderTokarev Don't get me wrong. I don't talk about configurations of components, or much worse of instances and moving them to initComponent(), that is not my point.

Now what I think about this.

initComponent() should resolve anything required the time an instance of this class is created. No more, no less.

You can mess up a load when defining classes and most of it happens because people don't understand how the ExtJS class-system works. As this is about components, the following will focus on those. It will also be a simplified example which should only show a sort of error that I've seen a lot of times.

Let's start: We have a custom panel which does a lot of nifty things. That brings up the need of a custom configuration, we call it foo. We add it along with our default config option to the class definition so we can access it:

Ext.define('Custom', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.custpanel',

    foo: {
        bar: null  
    },

    initComponent: function() {
        this.callParent(arguments);
    }
});

But things get weird after testing. The values of our configurations seems to change magically. Here's a JSFiddle What happened is that all created instances are referring to the same foo instance. But lately I've done that

store: {
    fields: [ ... ],
    proxy: {
        type: 'direct',
        directFn: 'Direct.Store.getData'
    }
}

with a store and that worked. So why doesn't foo work?

Most people don't see any difference between this little foo object and an (ExtJS) config which is basically correct because both are objects (instances). But the difference is that all classes shipped by sencha know perfectly well which configuration properties they expect and take care of them.

For example the store property of a grid is resolved by the StoreManager and can therefore be:

  • storeId string, or
  • store instance or
  • store configuration object.

During the initialization of the grid either of these get resolved and overwritten by an actual store instance. A store is just one example. I guess the more known one is the items array. This is an array at definition time and it gets overridden for each instance with a MixedCollection (if I am not mistaken).

Yes there is a difference between a class definition and the instance created from it. But we need to take care of any new property which contains a reference like the foo from above and that is not that complicated. Here is what we need to do to fix it for the foo example

Ext.define('Custom', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.custpanel',

    foo: {
        bar: null  
    },

    initComponent: function() {
        this.foo = Ext.apply({}, this.foo);
        this.callParent(arguments);
    }
});

Here's the JSFiddle

Now we take care of the foo config when an instance get created. Now this foo example is simplified and it will not always be that easy to resolve a configuration.

Conclusion

Always write your class definition as configurations! They must not contain any referred instances except for plain configuration and must take care of these to resolve them when a instance get created.

Disclaimer

I do not claim to cover all with this really short writing!

yuяi
  • 2,617
  • 1
  • 23
  • 46
sra
  • 23,820
  • 7
  • 55
  • 89
  • Now it is much clearer, thanks. Basically you get it right, the only missing piece is terminology. In Ext JS class system parlance, the term "property" means *instance property* or *static class property*. It's a runtime thing *only*. OTOH "config option", while technically a property of class definition object, has very different meaning semantically. It should be viewed as immutable "compile" time *statement*. (continued in next comment) – Alex Tokarev Jan 26 '13 at 19:39
  • Of course, technically it's not a statement and it's mutable but the idea is like this: declare your classes as immutable templates, define config options as part of those, and when an object of that class is instantiated, all config options become properties by default - unless you do something special with them, like above. So this way, `store` is notoriously wrong example: in theory, it should only accept either string storeId or store config object, because those are immutable. But it does also accept store instance, for hysterical raisins, and that is what confuses the heck out of people. – Alex Tokarev Jan 26 '13 at 19:42
  • So again, in the example above, when you're defining `foo` as config object, it's class "statement". When you init a copy of it in the `initComponent`, it becomes a property of that instance. Basically, that's what `initComponent` is for, except that some properties are better handled in constructor. – Alex Tokarev Jan 26 '13 at 19:47
  • P.S. In the second comment above, "all config options become properties by default" should be read as "all config options are shared by default". No coffee for me yet this fine morning. – Alex Tokarev Jan 26 '13 at 19:51
  • @AlexanderTokarev Lol ... _store instance, for_ **hysterical** _raisins, and that_ ... Nice typo. But I am totally with you. Thanks for that additional info. – sra Jan 26 '13 at 20:07
  • @AlexanderTokarev And for my terminology; see me as a user who learned it all by using the framework (since version 2). And I think that both is important: How you should treat it and how it can behaves. If I find time I will add your info to my post but If you like feel free to edit it yourself. And thanks again for your comments! :) – sra Jan 26 '13 at 20:15
  • 1
    Excellent post! I think we all agree on the rights and wrongs; the only trouble is that line such as `this.foo = Ext.apply({}, this.foo);` are not visible or clear within the ExtJS library. So how can users grasp these (rather complicated topic) without a proper Sencha article on it? – Izhaki Jan 27 '13 at 22:55
  • 3
    @Izhaki Thanks for the feedback. As most of their Dev-Team members always say: **don't be afraid of the sourcecode** I think this topic is rather hard to cover and sencha has made huge steps since 4.x with the focus on the new MVC pattern, performance and some layout-rendering bugs. So most of their topics cover this parts. And other important parts like event-delegate etc. I tell just everyone in my team to look into the sourcecode, it is all there and the API is one of the best. – sra Jan 28 '13 at 08:24
  • @AlexanderTokarev Would you please be so kind and take a look at this [JSFiddle](http://jsfiddle.net/S5BSp/5/) there seems to be some huge issues with lacy instantiation of selection models. – sra Feb 06 '13 at 09:39
  • @sra Not sure what issues you have in mind; I've opened that JSFiddle and it looked OK to me, grids rendering and plugins working as they are. Could you be more specific with that please? – Alex Tokarev Feb 06 '13 at 21:02
  • @AlexanderTokarev thanks for looking at this. Well, the Header for the checkboxcol is missing in the second one and the multiselect does not work. – sra Feb 07 '13 at 06:10
  • nice post. It seems to me the example above is overkill unless you want to include some custom properties for configuration. This has been great to read some of the back and forth - really helped me understand what is going on in extjs. I hadnt realised properties declared in the class templates were static – JonnyRaa Feb 07 '14 at 10:36
  • 1
    Is this still applicable to ExtJs 6 ? – Flying Gambit Jun 27 '16 at 07:20
  • @FlyingGambit Yes. The class system hasn't changed. – sra Jun 29 '16 at 20:28
14

I usually advocate for having as much configuration as possible in class config options, because it reads better and is easier to override in subclasses. Besides that, there is a strong possibility that in future Sencha Cmd will have optimizing compiler so if you keep your code declarative, it could benefit from optimizations.

Compare:

Ext.define('MyPanel', {
    extend: 'Ext.grid.Panel',

    initComponent: function() {
        this.callParent();
        this.store = new Ext.data.Store({
            fields: [ ... ],
            proxy: {
                type: 'direct',
                directFn: Direct.Store.getData
            }
        });
        this.foo = 'bar';
    }
});

...

var panel = new MyPanel();

And:

Ext.define('MyPanel', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.mypanel',

    foo: 'bar',

    store: {
        fields: [ ... ],
        proxy: {
            type: 'direct',
            directFn: 'Direct.Store.getData'
        }
    }
});

...

var panel = Ext.widget({
    xtype: 'mypanel',
    foo: 'baz'
});

Note how these approaches are very different. In the first example, we're hardcoding a lot: object property values, store configuration, MyPanel class name when it's used; we're practically killing the idea of a class because it becomes inextensible. In the second example, we're creating a template that can be reused many times with possibly different configuration - basically, that's what the whole class system is about.

However, the actual difference lies deeper. In the first case, we're effectively deferring class configuration until runtime, whereas in the second case we're defining class configuration and applying it at very distinctively different phases. In fact, we can easily say that the second approach introduces something JavaScript lacks natively: compile time phase. And it gives us a plethora of possibilities that are exploited in the framework code itself; if you want some examples, take a look at Ext.app.Controller and Ext.app.Application in latest 4.2 beta.

From more practical perspective, the second approach is better because it's easier to read and deal with. Once you grasp the idea, you will find yourself writing all your code like that, because it's just easier this way.

Look at it this way: if you would write an old style Web application, generating HTML and stuff on the server side, you would try not to have any HTML mixed with the code, would you? Templates to the left, code to the right. That's practically the same as hardcoding data in initComponent: sure it works, up to a point. Then it becomes a bowl of spaghetti, hard to maintain and extend. Oh, and testing all that! Yuck.

Now, there are times when you need to do something with an instance at runtime, as opposed to a class definition time - the classical example is applying event listeners, or calling control in Controllers. You will have to take actual function references from the object instance, and you have to do that in initComponent or init. However, we're working on easing this problem - there should be no hard requirement to hardcode all this; Observable.on() already supports string listener names and MVC stuff will too, shortly.

As I said in the comments above, I'll have to write an article or guide for the docs, explaining things. That would probably have to wait until 4.2 is released; meanwhile this answer should shed some light on the matter, hopefully.

Alex Tokarev
  • 4,821
  • 1
  • 20
  • 30
  • 1
    What you are totally missing to mention is that f.e. a grid handles this known property (store) in a special way and looks if it is either a string, store instance or config and after that **overrides** the property. This is a implemented special handling for special properties only. – sra Jan 24 '13 at 07:38
  • @sra You're confusing class configuration and runtime value. In the second example above, `store` is not a property but is a config option - because `Ext.define` deals with **classes** not **instances**. When an instance of a given class is created, properties are defined - but not sooner. This way, `store` is not a "special known property", it's a config option that happens to have the same name as the instance property that will be created at object instantiation time. – Alex Tokarev Jan 25 '13 at 18:38
  • I think you got me wrong so I took some time to write in short what I think about that. Please feel free to comment. (@Izhaki I think this will interest you too) – sra Jan 26 '13 at 12:39
  • This answer describes the way I *want* to use ExtJS 4, but I'm having to fight it. Would you mind taking a look at another question I recently asked in light of this recommendation? (http://stackoverflow.com/questions/14391404/pass-up-child-components-config-to-parent) – romacafe Jan 27 '13 at 18:02
  • @AlexTokarev It sortof seems like you want it to be the case that properties aren't defined until an instance is created and ext provides tools to make that work (and generally works in that fashion) but in actual fact things defined in the class templates become static properties/fields in the instances unless they are deliberately replaced – JonnyRaa Feb 07 '14 at 10:46
  • @JonnyLeads Yes, something along these lines. Ext JS 4 is kind of confusing in this regard; Sencha Touch is and Ext JS 5 will be much clearer and easier to understand. – Alex Tokarev Feb 07 '14 at 18:31
12

I've been searching for an answer to the same question when I ended up here and seeing these answers made me disappointed. None of these answer the question: initComponent() or constructor?

It's nice to know that class config option objects are shared and you need to initialize/process them per instance, but the code can go into the constructor as well as the initComponent() function.

My guess was that the constructor of the Component class calls initComponent() somewhere in the middle and I wasn't very wrong: Just had to look at the source code, it's actually AbstractComponent's constructor.

So it looks like this:

AbstractComponent/ctor:
- stuffBeforeIC()
- initComponent()
- stuffAfterIC()

Now if you extend a Component, you'll get something like this:

constructor: function () {
  yourStuffBefore();
  this.callParent(arguments);
  yourStuffAfter();
},
initComponent: function () {
  this.callParent();
  yourInitComp()
}

The final order these get called in is:

- yourStuffBefore()
- base's ctor by callParent:
  - stuffBeforeIC()
  - initComponent:
    - base's initComponent by callParent
    - yourInitComp()
  - stuffAfterIC()
- yourStuffAfter()

So in the end it all depends on whether you want/need to inject your code between stuffBeforeIC and stuffAfterIC which you can look up in the constructor of the class that your are going to extend.