2

Problem
I'm working with a Vue CLI application. There is a grandchild component that needs access to another component's element's properties. It really only needs the clientHeight property.

component structure

So Content2.vue component needs access to the clientHeight property of this element: <div id="header"></div> (in Header.vue).

Attempted Solutions

  • I've tried using $refs. And according to this stackoverflow answer $refs are only available to components directly related as parent/child.

  • I've also considered adding the $refs or clientHeight to the store (vuex), but its seems like overkill.

  • I could always go vanilla and use querySelector or getElementById. But thought there may be something better.

Thanks for your help!

Ryan
  • 510
  • 4
  • 16
  • It seems like you are aware of all the possible solutions. My personal recommendation would be to use `vuex`. You will get ***a lot*** of mileage out of learning and familiarizing yourself with `vuex`. It might seem like overkill for this particular use case, but I think a simple case like this might be the best way to learn how to use it. The added bonus is, then it's already in place for the next time you need to reach for it. – Vince Oct 01 '19 at 22:43
  • Thanks for you input @vince! Vuex is implemented in the project and a lot of the recommendations below are suggesting it too. Looks like that's the way to go. – Ryan Oct 02 '19 at 16:52

3 Answers3

9

Possible solutions as of today:

  • $refs

    • $refs are set on the component where they are declared. If you want to access it, you can do it via $parent, like this.$parent.$parent.$children[0].$refs.name.
    • Pros: quick and dirty. Works.
    • Cons: very fragile coupling. You can see from the train wreck not many programmers would find it a good solution.
  • Vuex

    • Get the clientHeight and add it to the store. (Don't add the $refs, they are not data objects)
    • Pros/Cons: seems overkill. And it is if you don't have Vuex yet and would only add it for this purpose. If this is not the case, the Vuex store is one of the "natural" places to store such global information as clientHeight. Hardly any new developer to your project will be surprised to know that clientHeight, which is shared by components far in the tree, is in the store.
  • Vanilla

    • Use querySelector or getElementById.
    • Pros/Cons: the problem here is the coupling. And is a hidden one, the worse kind. It's a quick and dirty solution which would be a good first alternative for prototyping and all. But this kind of hidden dependency is very dangerous when apps go mature.
  • this.$root: The root instance

    • Create a data() in the root component and have a clientHeight property there. You can now set it from any component using this.$root.clientHeight = newValue or read it using this.$root.clientHeight.
    • Pros: good if you don't want/need to have a full Vuex store for that. Easily accessible from any component in the tree. Reactive.
    • Cons: you have to declare the data() in the root component. Requires care as someone may begin to add properties there, which can quickly become a bad situation (having the downsides of Vuex without the upsides). If this happens, you should begin using Vuex.
  • Event Hub/bus

    • Create a Vue instance and use it as event hub/bus. This is similar to the $root solution, the difference being that instead of setting the data directly, you emit and receive events.
    • Pros: decoupled, no need for data() in the root.
    • Cons: still needs to create the instance that will act as hub/bus (although you could also use the $root for that). The bad part here is that this solution is not natural to your case. The setting of the clientHeight is not an event in any way (or is it?), so this could be unnatural.
  • Passing around props

    • Component passes the property up via events and down via props, one hop at a time. In other words, Header.vue passes clientHeight (via event) to App.vue, that passes down to Content.vue (via props) that passes further down to Content2.vue (via props).
    • Pros: no need for any global store or $root/hub/bus instances. Every data passing (in our out) is clear in the signature (events or props) of each component.
    • Cons: coupling all around; boilerplate just for passing around; arguably one of the main reasons stores like Vuex are used for.
  • Dependency injection via provide/inject aka "long-range-props"

    • Differently from props, you would pass (e.g. via event) the clientHeight from Header.vue to the App.vue and App.vue would declare a provide function. Then the Content2.vue would use inject in it. For more, see docs or demo.
    • Cons: not very common in Vue apps, some more novice devs may not know it. Still would have to pass clientHeight from Header.vue to App.vue via event, although that's just one hop.
    • Pros: the passing of the data from App.vue to Content2.vue is quite clean, after all, that's what provide was designed for.

Personally, I'd go for Vuex, specially if you already have it configured. If not, I'd use $root, but with the promise of using Vuex the first moment any other prop becomes necessary globally.

tony19
  • 125,647
  • 18
  • 229
  • 307
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • Wow, thank you @acdcjunior for your time and effort in writing such a thorough answer! Based on your response (and the others) I think Vuex is the way to go. – Ryan Oct 02 '19 at 18:16
2

If you are absolutely sure this can't be solved with CSS and you really do need the element's clientHeight, you are left choosing the least-bad solution from the following:

  1. Pass the id of the Header instance from App to Content to Content2. Then have Content2 use a query selector to get the height. This seems slightly less brittle than having Content2 have a hard-coded id.
  2. Have Header compute its own height and emit an event to App, which then passes the height to Content and then to Content2. Note that you may need to listen for resize events if the height is not fixed.
  3. Do either (1) or (2) but store it in Vuex or use an Event Bus instead of passing around params.

Which one you choose depends on more than what's presented in the question.

I'd choose (1) if no other components need to know the height of the Header.

I'd choose (2) if other components need to know the height of the Header or if the Header instance is in the best position to determine its own dynamic height.

I'd choose (3) if I was already using some sort of global state manager.

David Weldon
  • 63,632
  • 11
  • 148
  • 146
  • I was actually hoping to achieve this with CSS too. Not to bore you with too many details, but we're working with [Xterm.js](https://github.com/xtermjs/xterm.js/) and to make sure lines don't get cut off we need get some element heights. I like (2), as it will need to be resized. We're using vuex for the app, so it would be easy to add. Thanks so much for your help @david-weldon and taking the time to answer this! – Ryan Oct 02 '19 at 16:41
1

Well you can try to use something called "event bus". You can emit an event from any content over the global event bus to another content, and the other content listen to this event. Then you execute an function that emits over the event bus the height and u listen again with "on" on this event and execute an function

This might help you: https://alligator.io/vuejs/global-event-bus/

bill.gates
  • 14,145
  • 3
  • 19
  • 47
  • Thanks @Ifaruki for your feedback! Event bus is a good suggestion. After reading through the all the feedback, I'm going to use Vuex. – Ryan Oct 02 '19 at 18:18