Is mixin dead yet?

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 the load event for a child img.
  • 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

6 Likes

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.

2 Likes

If they’re not dead yet, I’ll beat them to death myself. I hate mixins.

3 Likes

what will be your replacement then?? there is only a few reusable methods we have.

1 Like

Perhaps you can offer a little more context about the alternative patterns. That would be a constructive way of helping OP.

1 Like

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}}
2 Likes

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.

2 Likes

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 ?

1 Like

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.

2 Likes

You should explain the consequences of doing such.

Splitting hairs here, but it isn’t “anymore” if they were never there :stuck_out_tongue_winking_eye: 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 :stuck_out_tongue_winking_eye: I quickly checked, there is no open issue or PR for it.

1 Like

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.

4 Likes