I am new to Vue and after checking the docs I can not figure out how to achieve the following:
pass an arbitrarily named variable as a prop to a component instance.
From my understanding, props are meant to be a way to allow data to be passed to a component and as it states on the website:
Passing Data to Child Components with Props:
Props are custom attributes you can register on a component. When a value is passed to a prop attribute, it becomes a property on that component instance.
Since props can be required, it would seem that we can design components under the assumption that some data would be there, and possible within certain parameters (if the validator option is specified).
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
This works if my named object of function has the exact same name as the prop to which I attempt to bind it. However, as I might have multiple instances of the Vue component and I might want to bind different data, I find using the same name for the variable less than ideal.
Now if I do as the Vue warning suggests, and name object / function the same as the prop, then the warning switches to that my data is not defined inside vue and to make sure it is reactive by reading: https://v2.vuejs.org/v2/guide/components-props.html
which, to be honest, doesnt really explain how to solve the issue,
or move the prop to the data level.
Which I can do (still gives the same warning), but kind of defeats the purpose of having props with my understanding of Vue.
This become more frustrating with anonymous vue instances.
e.g.
<script>
export default {
props: {
// records: {
// default: function(){return{}},
// type: Object
// }
},
data: function() {
return {
records: {} // define even an empty value in data for it to be 'reactive'
}
},
computed: {
fields: function() {
},
keys: function() {
return Object.keys(this.records)
}
},
methods: {
}
}
</script>
trying to use this as a component and set records to var myRecords = {"a": {}} fails:
<my-comp :records="myRecords"/>
So how exactly should I circumvent this? Where should I define my data then? and how should I handle the naming in the case of multiple instances?
A more fledged on example is found on a similar question:
Vue2: passing function as prop triggers warning that prop is already set
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
It's hard to give a definitive answer because I don't know the specifics of how you have organized your code. Are you using Webpack? Single file components (.vue)? If yes to any of these, then you needn't use global variables in the way you have described in your question.
Your entire Vue app should consist of a single root Vue instance (which you instantiate with new Vue(...), and from there each component is rendered within the root component's template, and templates of those components, and so on.
Looking at the following template:
<my-comp :records="myRecords"/>
myRecords must be a property on the Vue component instance whose template contains the above. It could be declared within the data block, or as a computed property, or a prop, it doesn't matter.
Here's a small example:
<div id="app">
<my-comp :records="myRecords"></my-comp>
</div>
// Obtain records in some way and store it in a global variable
var records = ...
// This is the root Vue instance
new Vue({
el: '#app',
data: {
// You must store the records array in the Vue component like this
// for it to be referenced within the template.
// You can optionally transform the data first if you want.
myRecords: records.filter(r => r.name.startsWith('Bob'))
// ^ ^
// | |
// | +--- the global variable
// |
// +---- the name of the property on the component instance
}
})
Note that MyComp component does not access the records global variable in any way, it only takes its input through the records prop.
Related
I don't need to pass data between a parent component to a child one or the opposite, I need something like php/c static variables.
I want my sfc (single file component) to have some data that is shared among all instances in in the page.
As far as I understand that's why in sfc we define data as a function
export default {
data(){
return {
// props here
};
}
}
while in page scripts we can define it as an object
const app = new Vue({
data: {
// props here
},
}
That's because since we can have multiple instances of a sfc in the page defining its data as a function make each instance to execute in and get its own data, while with page script we can have a singe instance.
I need to define some of my sfc data to be shared between component instances, while other data to be per-instance.
Is there a way to do this?
That depends on the data to be defined, its complexity, and purpose.
If these are 2 or 3 readonly variables, they can be set as global properties using Vue.prototype (Vue 2) or app.config.globalProperties (Vue 3). I'm not sure, because in your example you use Vue 2 syntax.
If the data should be reactive, you can set up a simple state management as explained in the Vue documentation: Simple state management.
If the data is more complex than that, the next step will be Vuex.
Following #Igor answer I looked after the simple state management and found the ref() method that creates reactive primitive values.
In my specific use case I needed to share among all the sfc instances just an array, so in my sfc I had:
const reactive_array = ref([]);
export default {
data() {
return {
shared_array: reactive_array,
};
},
};
I have a component that has complex rendering logic.
I try to carry out this logic to helper classes, for simplifying.
To do this, in the data section (for reactivity), I create class references as follows:
export default {
data: () => ({
state: new InitialState(this),
query: new QueryController(this)
})
}
As I understand it, at this point the context of this is not yet defined.
So, I have two questions.
1) Is there a way to pass the this component context in the data section (without lifecycle hooks)?
2) Is the approach with references to external classes of vuejs philosophy contrary?
Component instance is already available when data function runs, this is one of reasons why it has been forced to be a function.
Due to how lexical this works with arrow functions, it's incorrect to use them to access dynamic this. It should be:
data() {
return {
state: new InitialState(this),
query: new QueryController(this)
};
})
The problem with InitialState(this) is that the entire component instance is passed instead of relevant data, this breaks the principle of least privilege.
Despite Vue isn't focused on OOP, there's nothing wrong with using classes. One of possible pitfalls is that classes may not play well with Vue reactivity because it puts restrictions on the implementation. Another pitfall is that classes cannot be serialized to JSON and back without additional measures, this introduces limitations to how application state can be handled.
As I understand it, at this point the context of this is not yet defined.
Only because of the way you've written the code. The component instance does exist and is available. It is sometimes used to access the values of props for determining the initial values of data properties.
For example, here is an example from the documentation:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
export default {
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
}
The reason why your code doesn't work is because you are using an arrow function. If you change it to the following then this will be available:
export default {
data () {
return {
state: new InitialState(this),
query: new QueryController(this)
}
}
}
See also the note here:
https://v2.vuejs.org/v2/api/#data
Note that if you use an arrow function with the data property, this won’t be the component’s instance, but you can still access the instance as the function’s first argument
As to your other question about whether using classes like this is contrary to Vue...
I don't think the use of classes like this is encouraged but they can be made to work so long as you understand the limitations. If you have a clear understanding of how Vue reactivity works, especially the rewriting of properties, then it is possible to write classes like this and for them to work fine. The key is to ensure that any properties you want to be reactive are exposed as properties of the object so Vue can rewrite them.
If you don't need reactivity on these objects then don't put them in data. You'd be better off just creating properties within the created hook instead so the reactivity system doesn't waste time trying to add reactivity to them. So long as they are properties of the instance they will still be accessible in your templates, there's nothing special about using data from that perspective.
I think computed is a better way to do what you want
export default {
computed:{
state(){
return new InitialState(this);
},
query(){
return new QueryController(this);
}
}
}
Standard example of component in Vue looks like that:
Vue.component('blog-post', {
// camelCase in JavaScript
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
In basic docs about components there's no data object used, just props. However documentation of data mentions components:
Restriction: Only accepts Function when used in a component
definition.
There's even example:
var data = { a: 1 }
// direct instance creation
var vm = new Vue({
data: data
})
vm.a // => 1
vm.$data === data // => true
// must use function when in Vue.extend()
var Component = Vue.extend({
data: function () {
return { a: 1 }
}
})
Does it mean that component can have both data and props? Is data kind of private property (if we'd like to compare to OOP) and property public one?
Components can have both props and data. The difference between them is that props are received by the parent component while data are manipulated by the component. The props are static (changeable by the parent) while data are dynamic (but cannot be changed by the parent).
The only way a child component can affect a property is by emitting events. The parent component can listen to those events and receive and receive anything in the payload argument. In the listener function, they can change their own data and the child property will be updated as well. This is known as the one-way data flow:
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent’s state, which can make your app’s data flow harder to understand.
Vue documentation
Here's a little schema if you're visual:
I have a method in the root Vue instance that updates some data in the "data" object of that same Vue instance (see below).
Here is my function in the methods object of the root Vue instance
updateRecipe(newRecipe) {
this.recipesArr[this.findRecipeIndex(newRecipe.id)] = newRecipe;
}
Here is my data object in the root Vue instance
data: {
recipesArr: JSON.parse(localStorage.getItem("storedRecipes")) // this is an array filled with objects
}
I then have a Vue component which has a "recipesarr" property bound to "recipesArr" in the root instance's data object.
Here is my Vue component called "recipe-list"
<recipe-list
v-bind:recipesarr="recipesArr">
</recipe-list>
Unfortunately, this Vue component doesn't react to any changes made by my "updateRecipe" function to "recipesArr". Why is this the case and how do I get it to react to changes made to "recipesArr" in the root Vue instance?
As you can see in the image below, the "recipesarr" property has updated; however, the Vue component does not update when I change "servingsNum" from 4 to 2.
You've all fixed the problem, but we might as well write it down. Vue cannot detect changes to array members when you access them directly by index. Use splice(), or Vue.set() to modify array members, and your changes will be detected. so...
updateRecipe(newRecipe) {
// Don't do this.
this.recipesArr[this.findRecipeIndex(newRecipe.id)] = newRecipe;
// Do this instead !
this.recipesArr.splice(this.findRecipeIndex(newRecipe.id),1,newRecipe);
}
I have been working with the Component Factory Resolver for awhile and while I think it's pretty slick, there is one thing that drives me nuts. I would love to wrap most of the repeated code into a helper function that renders the component.
In my case I have a dashboard component where we render quite a few different components by altering singleton services to trigger visibility or not. Rather than having a ton of these create component code blocks, I was wondering if anyone has successfully create a helper-like function that a few variables can be passed into to achieve the same effect, thus eliminating a lot of the repetitive code.
Below is my attempt at a helper function and the call to activate it. The component gets created, but the destroy function doesn't work. I have narrowed it down to the Component Reference not actually being saved to the globally accessible component. Is there a way to store component references within a global array? If so how would you go about dynamically accessing them as components are added/destroyed?
Subscription within ngOnInit
// Subscribe to Create User Modal Visibility
this._ComponentVisibilityService.createUserVisibility$.subscribe(
_createUserVisibility => {
this.renderComponent(
this.createUserModal,
CreateUserComponent,
this.createUserModalContainer,
_createUserVisibility
)
}
)
Function within the dashboard component
renderComponent(component, template, container, visibility) {
if (visibility) {
// Destroy previously built components if not already destroyed
if (component) component.destroy();
// Generate component factory
const componentFactory = this._ComponentFactoryResolver.resolveComponentFactory(template);
// Render the component
component = container.createComponent(componentFactory);
} else {
// Destroy the component if visibility is false
if (component) component.destroy()
}
}
So ended up doing some digging last night and found that Typescript considers any to be a basic (primitive) type. So from what I can tell, none of the methods, or structure are available to component, unless altered to not fit into the "basic" category, otherwise its passed as a value and not a reference. However, like in javascript, an object is not considered a primitive type so I refactored the component variable to be cast as an object with a property component as ComponentRef<any>, and it worked!
Component Property Declaration
createUserModal: { component: ComponentRef<any> } = { component: null }
Function Declaration
renderModal(component: { component: ComponentRef<any> }, template, container, visibility) {
// Create Component...
}
This combination allows the object to passed in as a reference which in turn allows the function to alter the value of DashboardComponent property this.createUserModal, which in turn allows all of the methods to be called on it as normal.