I don't get why this "property changed" via #observable is not working, the stringValueChanged() method doesn't get called. What am I missing here? (The whole binding is not working here.)
currency-control.ts
import { bindable, observable } from 'aurelia-framework';
export class CurrencyControl {
#bindable
value: number;
#observable
stringValue: string;
constructor() {
}
valueChanged(newValue: number, oldValue: number) {
alert('value changed');
}
stringValueChanged(newValue: string, oldValue: string) {
alert('stringValueChanged changed');
}
}
currency-control.html
<template>
<require from="./currency-control.css"></require>
<div class="input-group">
<div class="input-group-addon">€</div>
<input type="text" maxlength="5" pattern="\d*" class="form-control" value.bind="stringValue" />
</div>
</template>
My fault! In the component that uses the CurrencyControl I required the CurrencyControl's html:
<template>
<require from="../currency-control/currency-control.html"></require>
instead of the component:
<template>
<require from="../currency-control/currency-control"></require>
Related
how can I pass class attribute from parent to child component element?
Look here:
https://codesandbox.io/s/pedantic-shamir-1wuuv
I'm adding class to the Component "Input Field"
And my goal is that the 'custom-class' will be implemented on the 'input' element in the child component
But just still using the class attribute, and not setting a new prop like "customClass" and accept it in the props of the component
Thanks!
This depends on the template structure of your ChildComponent
Your Parent Component can look like this:
<div id="app">
<InputField class="anyClass" />
</div>
If your Child looks like this:
<template>
<input ... />
</template
Because if you have only 1 root Element in your template this will inherit the classes given automatically.
if your Template e.g. looks like this: (only available in vue3)
<template>
<input v-bind="$attrs" />
<span> hi </span>
</template
You will need the v-bind="$attrs" so Vue knows where to put the attributes to. Another Solution would be giving classes as props and assigning it to the element with :class="classes"
The pattern for the customs form component in Vue 2 where the props go to a child element looks something like this.
<template>
<div class="input-field">
<label v-if="label">{{ label }}</label>
<input
:value="value"
:class="inputClass"
#input="updateValue"
v-bind="$attrs"
v-on="listeners"
/>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: "",
},
value: [String, Number],
inputClass: {
type: String,
default: "",
},
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
};
},
},
methods: {
updateValue(event) {
this.$emit("input", event.target.value);
},
},
};
</script>
The usage of those components could look something like this.
```html
<template>
<div id="app">
<InputField
v-model="name"
label="What is your name?"
type="text"
class="custom"
inputClass="input-custom"
/>
<p>{{ name }}</p>
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
data() {
return {
name: "",
};
},
};
</script>
A demo is available here
https://codesandbox.io/s/vue-2-custom-input-field-4vldv?file=/src/components/InputField.vue
You need to use vue props . Like this:
child component:
<template>
<div>
<input :class="className" type="text" value="Test" v-bind="$attrs" />
</div>
</template>
<script>
export default {
props:{
className:{
type:String,
default:'',
}
}
};
</script>
Parent component:
<div id="app">
<InputField :class-name="'custom-class'" />
</div>
Since you're using vue2 the v-bind=$attrs is being hooked to the root element of your component the <div> tag. Check the docs. You can put this wrapper on the parent element if you need it or just get rid of it.
Here is a working example
Another approach
There is also the idea of taking the classes from the parent and passing it to the child component with a ref after the component is mounted
Parent Element:
<template>
<div id="app">
<InputField class="custom-class" ref="inputField" />
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
mounted() {
const inputField = this.$refs.inputField;
const classes = inputField.$el.getAttribute("class");
inputField.setClasses(classes);
},
};
</script>
Child Element:
<template>
<div>
<input type="text" value="Test" :class="classes" />
</div>
</template>
<script>
export default {
data() {
return {
classes: "",
};
},
methods: {
setClasses: function (classes) {
this.classes = classes;
},
},
};
</script>
Here a working example
In Vue2, the child's root element receives the classes.
If you need to pass them to a specific element of your child component, you can read those classes from the root and set them to a specific element.
Example of a vue child component:
<template>
<div ref="root">
<img ref="img" v-bind="$attrs" v-on="$listeners" :class="classes"/>
</div>
</template>
export default {
data() {
return {
classes: null,
}
},
mounted() {
this.classes = this.$refs.root.getAttribute("class")
this.$refs.root.removeAttribute("class")
},
}
Pretty much it.
I started to work recently on a VueJS project (first time with that framework) and I face a problem.
I have an object (called "propObject") defined in a mother component. That propObject gets its value via a webservice, called in a beforeRouteEnter method in that mother component.
I have to pass this propObject to a child component so I can display what's inside (a "libelle" attribute, among other things). I tried to do it using v-bind and props but I didn't manage to make it work.
Here is my code :
Mother.vue
<template>
<div class="row justify-content-center">
<b-container>
<b-row>
{{propObject.libelle}}
<b-col> <cpm-child :prop-object="propObject"/></b-col>
[...]
</b-row>
</b-container>
</template>
<script lang="ts" src="./mother.component.ts"></script>
Mother.component
#Component({
components: {
'cpm-child': Child,
},
})
export default class Mother extends Vue {
#Inject('propObjectService') private propObjectService: () => propObjectService;
public propObject: IPropObjectClass = new PropObjectClass();
beforeRouteEnter(to, from, next) {
next(vm => {
if (to.params.propObjectId) {
vm.load(to.params.propObjectId);
}
});
}
public load(propObjectId: string): void {
this.propObjectService()
.find(propObjectId)
.then(res => {
this.propObject = res;
});
}
}
Child.vue
<template>
<div>
<span>
{{propObject.libelle}}
[...]
</span>
</div>
</template>
<script lang="ts" src="./child.component.ts"></script>
Child.component
export default class Child extends Vue {
props: {
propObject: IPropObjectClass,
}
}
propObject.model.ts
export interface IPropObjectClass {
code?: string;
libelle?: string;
[...]
}
export class PropObjectClass implements IPropObjectClass {
constructor(
public code?: string,
public libelle?: string,
[...]
) {}
}
My goal is to display the {{propObject.libelle}} in the child vue. In the Google Chrome's console, propObject is considered "undefined".
Last information : {{propObject.libelle}} is displayed correctly in the mother vue after a few seconds, so the propObjectService works as intended.
So far, nothing I tried worked, so any help would be greatly appreciated. If you need further clarification, don't hesitate to ask.
I created a sample with Vue 2 / Vue CLI showing a standard way of initializing a prop with data before rendering the child. You should be able to port it to your app.
The main takeaways are that you can call your data service in the parent (Mother) created() lifecycle hook. And by using the v-if directive, you child will not be rendered until the prop has been updated with data from the service call.
Parent.vue
<template>
<div class="parent">
<h4>Parent</h4>
<hr>
<child v-if="user" :userProp="user"/>
</div>
</template>
<script>
import axios from 'axios'
import Child from './Child.vue'
export default {
components: {
Child
},
data() {
return {
user: null
}
},
methods: {
getUser() {
axios.get('https://jsonplaceholder.typicode.com/users/1')
.then(response => this.user = response.data)
.catch(error => console.log(error));
}
},
created() {
this.getUser();
}
}
</script>
Child.vue
<template>
<div class="child">
<h5>Child</h5>
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-3 font-weight-bold">ID</div>
<div class="col-md-5">{{ user.id }}</div>
</div>
<div class="row">
<div class="col-md-3 font-weight-bold">NAME</div>
<div class="col-md-5">{{ user.name }}</div>
</div>
<div class="row">
<div class="col-md-3 font-weight-bold">USER NAME</div>
<div class="col-md-5">{{ user.username }}</div>
</div>
<div class="row">
<div class="col-md-3 font-weight-bold">EMAIL</div>
<div class="col-md-5">{{ user.email }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
userProp: {
type: Object,
required: true
}
},
data() {
return {
user: this.userProp
}
}
}
</script>
What's the problem
I wanted to assign a local component variable to prop. I constantly get Vue alert Invalid watch handler specified by key "undefined". Maybe the case is that the prop is passed from another component, where I use v-model, but I don't really know. I would really appreciate your help, because my small exercise project really depends on this mechanic.
Parent Component
Here I have some HTML select, this is where I actually model my state.selectedPhysicsModule
<template>
<div>
<div>
<h1>Some header</h1>
</div>
<form class="choosePhysicsModule">
<label for="selectedPhysicsModule"></label>
<select class="select_Module" id="selectedPhysicsModule" v-model="state.selectedPhysicsModule">
<option :value="option.value" v-for="(option, index) in importedListToSelect" :key="index">
{{option.name}}
</option>
</select>
</form>
<list_of_exercises v-if="state.selectedPhysicsModule" :what_exercises="state.selectedPhysicsModule"/>
</div>
</template>
<script>
export default {
name: 'ChoosePhysicsModule',
components: {list_of_exercises},
setup() {
const state = reactive({
selectedPhysicsModule: null,
})
return {
state,
importedListToSelect
}
}
}
Child Component
</script>
export default {
name: "list_of_exercises",
props: {
whatExercises: {
type: String,
required: true
}
},
data() {
return {
exercises: this.what_exercises,
}
},
watch: {
whatExercises: function () {
this.exercises = this.whatExercises
}
}
In the parent component where you are passing the prop you need to add a setter for the prop passed. Here is an example:
<template>
<div id="app">
<label>
<input name="whatExercises" v-model="whatExercises">
</label>
<ListOfExercises v-if="whatExercises" :what_exercises="whatExercises" />
</div>
</template>
<script>
export default {
data() {
return {
whatExercises: null,
}
}
}
</script>
P.S: as a side note, I recommend using camelCase for prop names. It's more in-line with the rest of the community. If you have time feel free to check out the style guide on the official website.
Here is some code that uses $set() to add a new reactive prop to the model. It works fine.
<template>
<div id="app">
<div>
Prop1: {{ x.prop1 }}
</div>
<div>
<input type="button" value="Go" #click="go()">
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
x: {}
};
},
methods: {
go() {
this.$set(this.x, 'prop1', 'yay');
}
}
};
</script>
Now, if I remove the x root property and try to add prop1 directly to the this it doesn't work.
<template>
<div id="app">
<div>
Prop1: {{ prop1 }}
</div>
<div>
<input type="button" value="Go" #click="go()">
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
};
},
methods: {
go() {
this.$set(this, 'prop1', 'yay');
}
}
};
</script>
I get that you should do this kind of thing, but I can't figure out why it doesn't work.
As stated in the docs:
The target object cannot be a Vue instance, or the root data object of a Vue instance.
It's a technical limitation.
I have a parent component in Vue called RecipeView, and it is an inline-component. inside it, i have these components:
comments. and inside comments, i have comment and NewCommentForm, has it shows in the picture below.
I am passing in the RecipeView component the id as a prop, and would like to access it in the NewCommentForm component in order to set an endpoint that i will post to and save the comment.
This is the RecipeView component:
<recipe-view :id="{{$recipe->id}}">
<comments :data="{{$recipe->comment}}"#added="commentsCount++"></comments>
</recipe-view>
and the script for it is this:
<script>
import Comments from '../components/Comments.vue';
export default {
props: ['initialCommentsCount','id'],
components: {Comments},
data(){
return {
commentsCount: this.initialCommentsCount,
recipe_id:this.id
};
}
}
</script>
The comments component looks like this:
<template>
<div>
<div v-for="comment in items">
<comment :data="comment"></comment>
</div>
<new-comment-form :endpoint="'/comments/**Here should go the id from parent RecipeView component**'" #created="add"></new-comment-form>
</div>
</template>
<script>
import Comment from './Comment.vue';
import NewCommentForm from './NewCommentForm.vue';
export default {
props: ['data'],
components: {Comment, NewCommentForm},
data() {
return {
items: this.data,
endpoint: ''
}
},
methods: {
add(comment) {
this.items.push(comment);
this.$emit('added');
}
}
}
</script>
and this is the NewCommentForm component:
<template>
<div>
<div class="field">
<p class="control">
<input class="input"
type = "text"
name="name"
placeholder="What is your name?"
required
v-model="name">
</p>
</div>
<div class="field">
<p class="control">
<textarea class="textarea"
name="body"
placeholder="Have your say here..."
required
v-model="body">
</textarea>
</p>
</div>
<button type="submit"
#click="addComment"
class="button is-medium is-success">send</button>
</div>
</template>
<script>
export default {
props:['endpoint'],
data(){
return {
body:'',
name:'',
}
},
methods:{
addComment(){
axios.post(this.endpoint, {
body:this.body,
name: this.name
}).then(({data}) => {
this.body = '';
this.name = '';
this.$emit('created', data);
});
}
}
}
</script>
Thanks for the help.