4

I am working on a Laravel/VueJS SPA that utilizes Vue / Vue-Router. I have a Vue component that has been assigned a $ref (inside Component C / Nested Child Component), that is encapsulated within the primary Vue-Router component (Component B) that is injected into the layout (Component A - Layout Component)

I need to access this element that corresponds to this $ref (inside component C), however I am unable to access it because the component hierarchy (within Component A)is constructed from information declared in Component B.

(Layout-Component / $root) - Vue Layout Component: --(Vue-Router-Component) -
Primary component injected into layout via Vue-Router - Child Component of
Component A --(dynamic-form-field) - Child Component of Vue-Router-Component

<Layout-Component ref="componentA">
  <Vue-Router-Component ref="componentB">
    <dynamic-form-field ref="componentC"></dynamic-form-field>
  </Vue-Router-Component>
</Layout-Component>

I tried accessing the $ref using the standard nested $ref syntax:

this.$root.$refs['componentB'].$refs['componentC'].attribute

However I am unable to access any $refs declared within Components B or C from Component A.


I determined that it is a Vue lifecycle issue, because if I recreate the nested component hierarchy (Components A - C) directly within the main layout (without using data declared within Component B) then I can access the nested $refs without issue via the above syntax.


The problem stems from the creation of the components in Component-A (Layout-Component) from data declared in Component B.

Layout Component (Component A) Snippet:

<template v-for="(input, field) in formDialog.inputs">
  <template v-if="Array.isArray(input)">
    <!-- for every record in the input array -->
    <template v-for="(inputArrRecord, arrIndex) in input">
      <v-col cols="12" class="p-0">
        <v-btn
          icon
          x-small
          v-if="arrIndex"
          class="float-right"
          color="error"
          :title="
            inputArrRecord.typeTitle
              ? `Remove ${inputArrRecord.typeTitle}`
              : 'Remove'
          "
          @click="inputArrRecord.removeAction"
        >
          <v-icon>mdi-close-box-multiple-outline</v-icon>
        </v-btn>
      </v-col>

      <template v-for="(inputArrRecordInput, field2) in inputArrRecord.inputs">
        <!-- for every field in the array record -->
        <dynamic-form-field
          :ref="`dynamicFormField${field2}`"
          :input="inputArrRecordInput"
          :field="field2"
          :mode="formMode"
          :error="formDialog.errors[`${field}.${arrIndex}.${field2}`]"
        ></dynamic-form-field>
      </template>
    </template>
  </template>

  <dynamic-form-field
    v-else
    :ref="`dynamicFormField${field}`"
    :input="input"
    :field="field"
    :mode="formMode"
    :error="formDialog.errors[field]"
  ></dynamic-form-field>
</template>

Data Declaration (Component B) Snippet:

export default {
  data() {
    return {
      formDialog: {
        errors: [],
        show: false,
        inputs: {
          id: {
            val: '',
            save: true,
            type: 'hidden',
          },

          word_data: [],
          definitions: [],

          tags: {
            val: [],
            save: true,
            add: true,
            type: 'autocomplete',
            items: this.$root.cache.tags,
            ref: 'vocabTagsAutocomplete',
            label: 'Search For a Tag',
            actionChange: this.addExistingTag,
            actionKeydown: this.addNewTag,
          },

          synonym: {
            val: '',
            save: true,
            add: true,
            placeholder: 'Synonyms',
          },
        },
        titleActions: [
          {
            icon: 'mdi-book-plus',
            btnType: 'text',
            text: 'Add Word Type',
            event: this.cloneWordDataTemplate,
          },
          {
            icon: 'mdi-book-plus-outline',
            btnType: 'text',
            text: 'Add Definition',
            event: this.cloneDefinitionTemplate,
          },
        ],
      },
    }
  },
}

dynamic-form-field.vue (Component C) Content:

<template>
  <v-col
    v-if="(mode == 'add' && input.add) || (mode == 'edit' && input.save)"
    :cols="input.gridSize || 12"
  >
    <form-field-selection
      :input="input"
      :field="field"
      :error="error"
      :ref="`formFieldSelection${field}`"
    ></form-field-selection>
  </v-col>
</template>

<script>
  export default {
    name: 'dynamic-form-field',
    props: ['input', 'field', 'mode', 'error'],
  }
</script>

How can I access the $ref declared within Component C from Component A?

2
  • Do you mind v-for makes an array of refs? Commented May 29, 2021 at 18:35
  • The array of refs doesn't affect being able to access the components by ref. Commented May 30, 2021 at 19:26

1 Answer 1

2
+50

Your component C could be emitting an event, sending itself as data:

// Component C
<script>
/*..component code..*/
mounted(){
  this.$root.$emit('child_ready',this)
}
</script>

Then you can hear that event in any other component:

// Your layout
<script>
/*...layout code...*/
created(){
    // Using created to make sure we add our listeners before any child mount
    this.$root.$on('child_ready',onChild)
},
beforeDestroy(){
    // cleaning the listener
    this.$root.$off('child_ready',onChild)
},
methods:{
    onChild(childRef){
        // Now you can access properties and methods
        // childRef.property()
        // childRef.method()
    }
}
<script>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.