Markus Johansson
Markus Johansson

Reputation: 3773

Set CSS class dynamically without template

For each component with prefix mycomponent- I would like to add a class with the name of the component. I don't want to have to modify the component in order to do this.

My first thought was to use mixins and somehow add the class in beforeCreate but I haven't managed to add classes dynamically without using the template.

Do I have to use $el.classList.add(this.$options.name) in beforeUpdate or similar? Is there some more Vue-ish way to do it?

Upvotes: 0

Views: 228

Answers (1)

tao
tao

Reputation: 90013

Here it is, wrapped up as plugin:

const addComponentNameAsClass = {
  install(Vue, options) {
    const fn = Vue.prototype.$mount;
    Vue.prototype.$mount = function() {
      fn.apply(this, arguments);
      if (this.$options._componentTag?.startsWith("mycomponent-")) {
        this.$el.classList.add(this.$options._componentTag);
      }
    }
  }
}

Vue.use(addComponentNameAsClass);

// that's all you need
// see it working:

['a', 'b', 'foo', 'whatever'].forEach(type => {
  Vue.component('mycomponent-' + type, {
    template: '<div><slot /></div>'
  })
});

new Vue({
  el: '#app'
})
[class^="mycomponent-"] {
  border: 1px solid;
  margin-bottom: 4px;
  padding: 1rem;
}

.mycomponent-a {
  border-color: red;
}

.mycomponent-b {
  border-color: blue;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<div id="app">
  <mycomponent-a>I should get a red border.</mycomponent-a>
  <mycomponent-b>I should get a blue one.</mycomponent-b>
  <mycomponent-foo>bar</mycomponent-foo>
  <mycomponent-whatever>Meh.</mycomponent-whatever>
</div>

Notes:

  • you should not expect this to work on Vue3. Why? Because whenever you're using internal props starting with _ Vue does not guarantee they'll still be there in the next major version update. But, on the other hand, the name of the component is not saved anywhere else (other than $options._componentTag).
  • the above won't work if you use components as <MycomponentA></MycomponentA>. However, you can swiftly get around it by running the value of $options._componentTag through a helper function (e.g: kebabCase from lodash).
    • note on note: if you want the added class to always be kebab-case, you'll have run the value passed to .classList.add() through kebabCase, as well). Otherwise, <MycomponentA> will add MycomponentA class and <mycomponent-a> will add mycomponent-a class, for obvious reasons.

Ref. "Vue-ish way": whatever the end goal of applying this "component" class is, chances are it can be achieved cleaner.
The very idea of placing classes denominating component type doesn't feel Vue-ish at all.
It feels WordPress-ish and Angular-ish. To me, at least.

Upvotes: 1

Related Questions