Home > database >  Vue: modify props of slotted component programmatically
Vue: modify props of slotted component programmatically

Time:10-04

Question

How does one modify a prop of a slotted Element at runtime?

Do I use the wrong lifecycle methods?
Are changes to slottedElement.type.props necessary as well?

Example

A.vue

<template>
    <B key="tag-outer">
        <B key="tag-inner" />
    </B>
</template>

<script>
import B from "./B.vue";

export default {
    name: "A",
    components: {
        B,
    },
};
</script>

B.vue

<template>
    <p>{{ mode }}</p>
    <slot />
</template>

<script>
export default {
    name: "B",

    props: {
        mode: {
            type: String,
            default: "outer",
        },

        key: String,
    },

    computed: {
        hasSlot() {
            return !!this.$slots.default;
        },
    },

    beforeMount() {
        this.modifySlottedElements();
    },
    beforeUpdate() {
        this.modifySlottedElements();
    },

    methods: {
        modifySlottedElements() {
            if (this.hasSlot) {
                this.$slots.default().forEach((slottedElement) => {
                    if (slottedElement.type.name === "B") {
                        // prevent concurrency issues
                        const copiedSlottedElement = JSON.parse(JSON.stringify(slottedElement));
                        console.log("before");
                        console.log(copiedSlottedElement);

                        slottedElement.props.mode = "inner";

                        console.log("modified");
                        console.log(slottedElement);
                    }
                });
            }
        },
    },
};
</script>

Output

before

type = Object {name: "B", props: Object, computed: Object, methods: Object, __file: "C:/dev/git/csx-vue/src/demo/test/B.vue", ...}
props = Object {key: "tag-inner"}
...
modified

type = Object {name: "B", props: Object, computed: Object, beforeMount: Function, beforeUpdate: Function, ...}
props = Object {key: "tag-inner", mode: "inner"}
...

Rendered

<p>outer</p>
<p>outer</p>

JSFiddle

Thank you


Workaround

Using an inverted logic (checking the parent)

<template>
    <p>{{ modeProxy }}</p>
    <slot />
</template>

<script>
export default {
    name: "B",

    props: {
        mode: {
            type: String,
            default: "outer",
        },

        key: String,
    },

    data() {
        return {
            modeProxy: this.mode,
        };
    },

    beforeMount() {
        this.markSubMenu();
    },
    beforeUpdate() {
        this.markSubMenu();
    },

    methods: {
        markSubMenu() {
            if (this.$parent.$.type.name === "B") {
                this.modeProxy = "inner";
            }
        },
    },
};
</script>

CodePudding user response:

Easiest way to solve your problem is to use inject/provide

  1. Your menu component will use inject to get it's level from the parent (if there is one)
  2. and provide to provide the information about the level for it's child components
<template>
  <div>
    Level: {{ menuLevel }} / {{ menuLevel2 }}
    <slot></slot>
  </div>
</template>

<script>
import { provide, inject } from 'vue';

export default {
  name: 'NestedMenu',
  setup() {
    // using Composition API
    let menuLevel = inject('menuLevel', 1 /* this is default value */);
    provide('menuLevel', menuLevel   1);

    return { menuLevel };
  },
  // Same functionality as above but using Options API
  inject: {
    menuLevel2: { default: 1 },
  },
  provide() {
    return {
      menuLevel2: this.menuLevel2   1,
    };
  },
};
</script>
  • Related