Understanding props in vue.js - javascript

I'm working through the guide for learning vue.js, got to the section on props, and ran into a question.
I understand that child components have isolated scops and we use the props configuration to pass data into it from the parent, but when I try it out I can't get it to work.
I have the example I'm working on up on js fiddle:
var vm = new Vue({
el: '#app',
// data from my parent that I want to pass to the child component
data:{
greeting: 'hi'
},
components:{
'person-container':{
// passing in the 'greeting' property from the parent to the child component
props:['greeting'],
// setting data locally for the child
data: function (){
return { name: 'Chris' };
},
// using both local and parent data in the child's template
template: '<div> {{ greeting }}, {{ name }}</div>'
}
}
});
When I run the above code, my output is only:
, Chris
The data local to the child component renders fine, but the passed in parent's data is either not coming through or is not properly rendering.
I don't see any errors in the javascript console and the template is rendering.
Am I misunderstanding how the props are supposed to be passed in?

You have to bind the value to the component prop like this:
<person-container v-bind:greeting="greeting"></person-container>
Jsfiddle
https://jsfiddle.net/y8b6xr67/
Answered here:
Vue JS rc-1 Passing Data Through Props Not Working

I've updated your fiddle
<person-container :greeting="greeting"></person-container>
You need to pass props from the parent to the child on the html component.

You can also pass any string to "greeting" by just setting it like normal html attribute, without using v-bind directive.
<person-container greeting="hi"></person-container>
Will also work. Note that anything you pass that way will be interpreted as plain string.
<person-container greeting="2 + 2"></person-container>
Will result in "2 + 2, Chris".
More in user guide: https://v2.vuejs.org/v2/guide/components.html#Props

Related

Declaring data in Vue with components

According to the docs, this is how you declare data in Vue:
data: {
name: 'Vue.js'
}
However, when I do that it doesn't work and an error shows in the console:
The "data" option should be a function that returns a per-instance value in component definitions.
I change it to the following and then it works fine:
data() {
return {
name: 'Vue.js',
}
}
Why do the Vue docs show the top bit of code when it doesn't work? Is there something wrong on my end?
Edit: This only happens when using components.
In a root Vue instance (which is constructed via new Vue({ . . . }), you can simply use data: { . . . } without any problems.
When you are planing to reuse Vue components using Vue.component(...) or using "template" tag, Use data attribute as a function.
Please review the corresponding section of the Vue.js documentation for more information regarding this problem
https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function
You should declare data in Vue.js by doing
var app = new Vue({
el: '#app', //This is the container in which Vue will be in. The #app means the id of the container is app
data: {
}
});
It turns out you need to declare data in components different than when you set it on a Vue object.
Instead, a component’s data option must be a function, so that each instance can maintain an independent copy of the returned data object:
More: Vue docs

Right way to transfer data from Child to Parent Component in Vue 2.4?

Hi I am new to Vue and I wanted to ask a question about binding parent component value to a child component and when that value changes in the child I want to to change for the parent.
I made a JS Fiddle showing where I accomplish this but I am confused as to why it works the way it does. If I pass an Object to the prop it will bind the values, but if I pass them as is, I get the 'mutation' error.
JS:
var demo = new Vue({
el: '#demo',
data: {
Name: { firstName: "Bonjur", secondName: "Captain"},
firstName: "Hello",
secondName: "World",
}
});
Vue.component('working', {
template: '<div>' +
'First: <input v-model="names.firstName" />' +
'Second: <input v-model="names.secondName" /> ' +
'</div>',
props: {
names: null
}
})
Vue.component('broken', {
template: '<div>' +
'First: <input v-model="first" />' +
'Second: <input v-model="second" /> ' +
'</div>',
props: {
first: null,
second: null
}
})
HTML
<div id="demo">
<working :names="Name"></working>
<broken :first="firstName" :second="secondName" ></broken>
<p> {{Name.firstName}} {{Name.secondName}} </p>
<p> {{firstName}} {{secondName}} </p>
</div>
My original goal was to have the 'demo' Vue object be the main data object I was going to post back to the server, and have several components that edit specific parts of data in the main 'demo' object in their own unique way, so really just a separation of concerns.
Any comments/opinions/suggestions on designing an app this way using Vue are welcome.
I have also read that you can listen to the child's events and change the parent value that way.
How this sort of Child to Parent data binding was designed or intended to be done in Vue js v2.4?
Why does the above example works the way it does?
It working when you pass the object because in JavaScript:
Objects are passed by reference
Primitives are passed by value
you an checkout this to know about passed by reference and passed by value
When you pass the object as a prop and change it in the child component the passed object is mutated as only the memory location of the object is passed not a new object.
when you are passing the primitives as props you are creating a new copy and is received by child component.
That the reason when you change the value in the broken component the newly copied value is changed not the original one in the root instance.
To make it work you should emit an event to parent component from child component . See custom events.
Its recommended to use events to mutate the props received from parent .
Here is the updated fiddle

vue JS not propagating changes from parent to component

I am pretty new to Vue Framework. I am trying to propagate the changes from parent to child whenever the attributes are added or removed or, at a later stage, updated outside the component. In the below snippet I am trying to write a component which shows a greeting message based on the name attribute of the node which is passed as property from the parent node.
Everything works fine as expected if the node contains the attribute "name" (in below snippet commented) when initialized. But if the name attribute is added a later stage of execution (here for demonstration purpose i have added a set timeout and applied). The component throws error and the changes are not reflected . I am not sure how I can propagate changes for dynamic attributes in the component which are generated based on other events outside the component.
Basically I wanted to update the component which displays different type of widgets based on server response in dynamic way based on the property passed to it .Whenever the property gets updated I would like the component update itself. Why the two way binding is not working properly in Vuejs?
Vue.component('greeting', {
template: '#treeContainer',
props: {'message':Object},
watch:{
'message': {
handler: function(val) {
console.log('###### changed');
},
deep: true
}
}
});
var data = {
note: 'My Tree',
// name:"Hello World",
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
function delayedUpdate() {
data.name='Changed World';
console.log(JSON.stringify(data));
}
var vm = new Vue({
el: '#app',
data:{
msg:data
},
method:{ }
});
setTimeout(function(){ delayedUpdate() ;}, 1000)
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
<greeting :message="msg"></greeting>
</div>
<script type="text/x-template" id="treeContainer">
<h1>{{message.name}}</h1>
</script>
Edit 1: #Craig's answer helps me to propagate changes based on the attribute name and by calling set on each of the attribute. But what if the data was complex and the greeting was based on many attributes of the node. Here in the example I have gone through a simple use case, but in real world the widget is based on many attributes dynamically sent from the server and each widget attributes differs based on the type of widget. like "Welcome, {{message.name}} . Temperature at {{ message.location }} is {{ message.temp}} . " and so on. Since the attributes of the node differs , is there any way we can update complete tree without traversing through the entire tree in our javascript code and call set on each attribute .Is there anything in VUE framework which can take care of this ?
Vue cannot detect property addition or deletion unless you use the set method (see: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats), so you need to do:
Vue.set(data, 'name', 'changed world')
Here's the JSFiddle: https://jsfiddle.net/f7ae2364/
EDIT
In your case, I think you are going to have to abandon watching the prop and instead go for an event bus if you want to avoid traversing your data. So, first you set up a global bus for your component to listen on:
var bus = new Vue({});
Then when you receive new data you $emit the event onto the bus with the updated data:
bus.$emit('data-updated', data);
And listen for that event inside your component (which can be placed inside the created hook), update the message and force vue to re-render the component (I'm using ES6 here):
created(){
bus.$on('data-updated', (message) => {
this.message = message;
this.$forceUpdate();
})
}
Here's the JSFiddle: https://jsfiddle.net/9trhcjp4/

looking up in store from a component

I have a template that includes a component.
// pods/workgroup/template.hbs
...
{{workgroup/member-add
wgId=model.id
store=store
peekUser2Workgroup=peekUser2Workgroup
}}
...
Within that component I need to lookup if something is already present in the store.
//somewhere in components/workgroup/member-add/componsent.js
let alreadyInStore = this.store.peekRecord('user2workgroup',u2wId);
I made it work by injecting the store into the component (as above), which of course is bad practise.
So I tried making a property in my parent-controller that does the store lookup:
//in components/workgroup/member-add/componsent.js
let alreadyInStore = this.get('controller').peekUser2Workgroup(u2wId);
//in pods/workgroup/controller.js
peekUser2Workgroup: function(u2wId) {
console.log(this);
console.log(this.store);
return this.store.peekRecord('user2workgroup',u2wId);
}
This works fine as long as I pass the complete store into the compentent as above.
However, if I don't pass the store to the component it get's undefined, although never accessed from the component directly (the store is present in the controller alone).
Logging into console of this gives me surprisingly the component, not the controller, this.store is undefined.
So I've learned, that with this I don't access the controller itself when a function/parameter gets called from outside/a component.
The question is, how can I make the controller to reference to itself with this?
Or how can I access the store when calling a parameter from outside?
Do I really need to pass the controller itself to himself??
like so:
// in component
let alreadyInStore = this.get('controller').peekUser2Workgroup(this.get('controller'), u2wgId);
//in controller
peekUser2Workgroup: function(myself, u2wId) {
console.log(this);
console.log(this.store);
return myself.store.peekRecord('user2workgroup',u2wId);
}
That seems very odd to me, and looks like I'm shifting around even more than I did initially when simply injecting the store to the controller...
Ember: 2.0.1
Ember-Data: 2.0.0
Instead of passing the store into the component as a property, inject it using Ember.service like this:
store: Ember.service.inject()
Then instead of passing in the function, just pass in the id vale you're looking up:
{{workgroup/member-add
wgId=model.id
}}
Now in your component you can fetch the record:
workgroup: function(){
return this.get('store').peekRecord('user2workgroup', this.get('wgId'));
}.property()

Why is there no binding between React.Js template and Angular model?

I am trying to mix Angular and React.JS. You can see my code here. Most examples I have seen involve an input written in basic Angular, and then output written in React like in this example. I am trying to do the opposite. I have two input fields as React components created in a directive and then an output of the model done with Angular.
I cannot get the output to reflect changes in the model. The model being $scope.formData = {name: "name", email: "email"};
I tried to follow the example in this video, and create an isolated scope in the directive
scope: {
formname: '=formname',
formemail:'=formemail'
},
and then pass the proper values through to the React template. It doesn't seem to have any affect. My input is not connected to the formData. In my non-React version of this, the model data fills in the input immediately and the output reflects changes to the input.
Because binding that data in an Angular style is breaking, I tried to have the React components update the model in a more Reactish way. I wrote a function in the controller called update_model,
$scope.update_model = function(data_type, updated_data){
$scope.$apply(function() {
$scope.formData[data_type] = updated_data;
});
console.log($scope.formData);
}
I then passed that function through the directive to the React template using scope.update_model, in the link function.
link: function(scope, element) {
React.renderComponent(Form_Profile_Component({update_model: scope.update_model, name_label: 'name', email_label: 'email', name_data: scope.formname, email_data: scope.formemail}), element[0]);
}
Now in the template, the input elements have and onChange event detector that will call this, and update the Model. The Model is supposed to be displayed at the bottom of the page with
<pre>
{{ formData }}
</pre>
The console.log shows a change in formData, but the {{formData}} display does not change.
Why won't my display {{formData}} show what is going on in the actual formData object?
EDIT: Just to make sure there wasn't a problem with {formData} I threw <input ng-model="formData.name"> in a few places in form.html, and there was no problem. The two way binding worked there.
EDIT 2: I tried adding the code below to the directive.
link: function(scope, element) {
scope.$watch('formData.name', function(newVal, oldVal) {
console.log("---");
React.renderComponent(Form_Profile_Component({update_model: scope.update_model, name_label: 'name', email_label: 'email', name_data: scope.formData.name, email_data: scope.formData.email}), element[0]);
});
}
I believe that when there is a change in formData, it will call this function and update the React component, or at the very least print '---' to the console. When I use the React input or the proper input nothing happens, which is not so surprising at this point. But when I tried the working input from the first edit, which does alter {{formData}}, nothing happens. It doesn't trigger the watch at all, I don't understand why.
You're on the right track with your 2nd edit where you put the renderComponent call inside a scope.$watch. This will make sure that the react component is re-rendered every time your watch variable formData.name changes.
However if you're looking for the changes you make within your React Component to propagate back out to angular, you need to make sure that the callback function you're passing to react has scope.$apply() call in it. Either encapsulating the change, or just after it.
Here's a quick example:
In your controller:
$scope.myVar = 'foo';
$scope.setVar = function(value) {
$scope.myVar = value;
$scope.$apply();
};
In your directive's link function (assuming no restricted scope, else change name as needed)
scope.$watch('myVar', function() {
React.renderComponent(
MyComponent({
myVar: scope.myVar,
setVar: scope.setVar
}),
element.get(0)
);
});
Then just call the this.props.setVar function from within your react component with the new value and it'll update correctly on the model.

Categories

Resources