Reputation: 243
I'm using vue-property-decorator lib. I've got 2 components: parent and child, child is a component that has a prop 'modalHeader':
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component({
name: "modal"
})
export default class Modal extends Vue {
@Prop({default: "Header"}) public modalHeader!: string;
}
</script>
Child component's template:
<template>
<div
class="modal fade"
id="detailsModal"
tabindex="-1"
role="dialog"
aria-labelledby="detailsModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="detailsModalLabel">
{{ modalHeader }}
</h5>
</div>
...
</div>
</div>
</div>
</div>
</template>
I have a property 'buttonPressed' in my parent component and I want to bind its properties to child prop, thus every time depending on the button pressed I'll get a different header for modal:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import Modal from '../modal/Modal.vue';
interface ControlButtonSetting {
id: string,
name: string,
enabled: boolean,
isDanger: boolean,
okText?: string,
componentType?: IModalComponent,
}
@Component({
name: "admin-dashboards-page",
components: {
"modal": Modal
}
})
export default class AdminDashboardsPage extends Vue {
private buttonsArray: ControlButtonSetting[] = [
{ id: "removeButton", name: "Remove", enabled: false, isDanger: true, okText: "Remove" }
];
public buttonPressed!: ControlButtonSetting;
private showModal = false;
created(): void {
this.buttonPressed = this.buttonsArray[0];
}
public controlButtonClicked(button: ControlButtonSetting): void {
this.buttonPressed = button;
this.showModal = true;
}
}
</script>
Parent component's template:
<template>
<div>
...
<!-- Modal -->
<modal
:modal-header.sync="buttonPressed.name"
<template v-slot:modalBody>
<component
:is="displayComponent()"
:dashboard-id="selectedListItemId">
</component>
</template>
</modal>
</div>
</template>
However, the 'modalHeader' value is set only once when the page loaded, but when 'selectedButton' object is changed, 'modalHeader' value is not being updated on the child component.
What helped me is to define parent's object as a Prop()
:
@Prop() public buttonPressed!: ControlButtonSetting;
This way child component's prop is updated whenever parent's prop is updated. But I start to see this message in a console:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "buttonPressed"
P.S. One guess I have is that I'm not mutating bound property in parent but replacing it with a new object, that's why parent-child binding may not work. Gonna check it out.
Upvotes: 3
Views: 547
Reputation: 6909
In short: your buttonPressed
data isn't reactive because it is not initialised. It has to be properly initialised for Vue to mark it as reactive.
public buttonPressed!: ControlButtonSetting; // not initialised
You have two options to initialise it properly:
#1 You give it a proper default value, even empty, directly after declaring it.
public buttonPressed: ControlButtonSetting = {
id: "",
name: "",
enabled: false,
isDanger: false,
okText: ""
}
#2 If you want its default value to rely on other data / props, use the data
factory. (Don't use created
hook for this)
public buttonPressed!: ControlButtonSetting; // use !: to tell typescript it's declared but in another place
// Even using `vue-property-decorator`, this will work and merge both data() and class properties
data () {
return {
buttonPressed: this.buttonsArray[0],
}
}
Don't mark buttonPressed
as a prop, because it isn't. And a Vue prop can't be updated from childs, it would break the reactivity of the parent.
Note: Vue props should always have a type provided. Even using vue-property-decorator
, you have to specify it.
@Prop({ type: String, default: 'Header' }) <- Vue prop type, only for runtime
readonly modalHeader!: string // <- typescript type, only for build time
(I advise you to use readonly
keyword to tell Typescript this property cannot be updated, because props are immutable in Vue)
Upvotes: 2