Hello guys, I have been thinking about using mixin in my Ember work. However in the guide (2.7.0), nowhere in the guide mention about mixin anymore. Is mixin going to be deprecated? Should I use, say services, to replace mixin? Thanks.
No, mixins are not dead. We use them in our app to reuse very specific behaviors. I see a lot of addons doing the same.
I would caution you from getting carried away with mixins. They can be an anti-pattern if used improperly. Generally we use them to define some reusable behavior pattern. A few examples:
-
WindowDidResize
: Hooks into events on the window. Fires hook on component. -
ImageOnLoad
: Hooks into theload
event for a childimg
. -
RealtimeMixin
: Subscribes to realtime messages.
Again, Iâm trying to stress that these are all very concise behaviors. Theyâre arenât for reuse of a specific page or component (e.g. UserProfileMixin
, SettingsPageMixin
). I personally have found that if you have code that is specific to a page or portion of your app, a better reuse pattern is simple object inheritance. For example:
// app/components/settings-base.js
import Ember from 'Ember';
export default Ember.Component.extend({
/* Shared by all settings pages. Usually computed props and actions */
});
// app/components/settings-user-page.js
import SettingsBaseComponent from './settings-base';
export default SettingsBaseComponent.extend({
/* Specifics for the user settings page */
});
Another great pattern for this is using components in block form and yielding data.
DISCLAIMER: These are just my opinions.
â Wesley
One rule of thumb I use is to narrow down mixins to very specific reusable tasks and have the mixin name start with a verb, like:
- WillSetNavigation
- HasAccessControl
- FiltersCertainData
Still, itâs kinda weird that the Ember Guides donât talk about mixins.
If theyâre not dead yet, Iâll beat them to death myself. I hate mixins.
what will be your replacement then?? there is only a few reusable methods we have.
Perhaps you can offer a little more context about the alternative patterns. That would be a constructive way of helping OP.
Components.
This may be hard to understand without first un-learning inheritance (Mixins are inheritance).
Ember component yield with block params is a very powerful concept. The same concept exists in Angular and React as well, but they go by different names. If functional programming is about passing functions into functions, then component programming is about passing components into components. Also, components and helpers are pretty much the same thing. In fact, the new module unification RFC stop making distinction between the two.
Keep in mind, your component doesnât have to actually render anything. You can use it as an abstraction. If youâre bothered by the extra div
tag, set the componentâs tagName: ''
.
A few example of refactoring @workmanwâs mixins into components.
{{#window-resize as |width height|}}
{{components width height}}
{{/window-resize}}
{{#image-load as |state|}}
{{#if state.isPending}}
Pending!
{{else if state.isRejected}}
Error!
{{else}}
Loaded!
{{/if}}
{{/image}}
{{#realtime channel="..." as |stream|}}
{{steam}} message!
{{/realtime}}
In some cases, I think that {{yield}}
does make sense. In some cases I donât think it does. I try to draw the line with behaviors. window-resize
is a behavior that a specific component might want to know about.
Using a component that yields data to another component for window-resize
feels odd to me because youâve created a dependency that will be surfaced to the consumer. If Iâm writing a masonry addon, and my masonry layout requires knowledge of a window resize, I obviously wouldnât want the consumer of the addon to care about this. They would have to do:
{{#window-resize as |width height|}}
{{my-masonry-component width height}}
{{/window-resize}}
Now sure, I could create a 3rd component that would wrap both my-masonry-component
and window-resize
together, but I would argue this is more of an anti-pattern than using a mixin.
Addons are not the only thing that has this either. Imagine a highly reusable component in your application that needs to know about window-resize
. Again, you either create an external dependencies or you create a 3rd component to wrap it up.
Again, Iâm not saying that there arenât cases for what youâre suggesting. We do this sometimes with âmultiselectionâ and other behaviors. But I donât thinking killing mixins and object inheritance is solving a problem. The less tools in your shed, the more you end up trying to fit a square peg into a round hole.
what about the case that i want to abstract certain functionalities that share between controllers , services not just components?
For example I want to call GPS Locator functions , when user press a button , when services have certain command , when entering the map route. and i want to abstract it in such a way ?
In such case, I think the masonry addon is trying to do too much. The responsibility of the masonry addon should be limited to laying out child content. Trying to do too much actually makes your component less composeable and therefore less reusable. For example, what if the consumer need a fixed width or height on the masonry container?
My opinion is donât create that 3rd component.
Sure, writing it this way can be more verbose. But on the flip side, the relationship and data flow is obvious at a glance. Therefore, code written this way is easier to reason about the programâs behavior.
Ah, the awesomeness of closure action.
{{! gps-locator.hbs }}
{{yield
position
(action "poll")
}}
// gps-locator.js
...
didInsertElement() {
// enter route means the component is inserted to DOM
this.send('poll');
}
...
{{#gps-locator as |position poll|}}
{{position}}
<button onclick=poll>
{{/gps-locator}}
Alternatively, you can put the behavior in a service. The service can be injected into components, controllers, or other services. YeaâŚitâs not component.
To be honest, seeing components as a replacement for mixins seems like a horrible idea to me, especially in the case of very specific logic that in no way is bound to the DOM. I use helpers for transforming data in a functional programming way, but I donât want to pollute my templates with logic that shouldnât be there, nor do I want to make a web of components depending on parent components for things like states or window size. In my opinion, this totally destroys the concept of components being isolated and independent reusable pieces of code and representation. Also, I have to disagree with mixins being inheritance. Theyâre really not.
Alternatively, you can put the behavior in a service. The service can be injected into components, controllers, or other services.
I agree on this one. That will keep your code seperated from the representation and doesnât make htmlbars your primary code âlanguageâ. Still I use mixins a lot for route level logic that could be moved to a service but would cause boiler plate code all over the place.
You should explain the consequences of doing such.
Splitting hairs here, but it isnât âanymoreâ if they were never there There isnât a particular reason why they arenât, other than having fallen through the cracks. The API documentation should provide the necessary info, and the mixin generator is still in Ember CLI. We also very tangentially mention mixins via EmbeddedRecordsMixin, since itâs a mixin thatâs provided by the framework itself.
I hadnât even realized until reading this thread I quickly checked, there is no open issue or PR for it.
Resetting scroll is a good example for generalists who are new to this type of thing: Resetting scroll on route changes - User Interface and Interaction - Ember Guides A mixin was used in this older cook-book. Would you extend the route or what in this case?
Component would still work, or you can use a combination of component and service.
// app/components/scroll-reset.js
...
didInsertElement() {
this._super(...arguments);
window.scrollTo(0, 0);
}
...
The reason this works is because the component rendering implies the route is successfully activated. You can even make it conditional.
{{#if (equal page 2)}}
{{scroll-reset}}
{{/if}}
That is an interesting way to think about it. It seems a bit strange, though. Iâm trying to think of a rule, and the closest I can get is trying to tie something to an outlet. Iâve done it with a mixin, and by extending the route - but when you get 20 routes, that isnât very dry. It would be nice to say âany top-level route of x outlet: resetScroll.â I wouldnât want to make a âpageâ component per say. It seems like each way - still involves writing something many times over.
Here another resetScroll implementation with a component: See Joshâs comment at the bottom: Ember.js â Scroll to the Top on Every Page Load
Not Ember, but still relevant.