Here is my base App.vue that holds the router-view:
<template>
<div>
<Navbar/>
<div class="container-fluid">
<router-view></router-view>
</div>
</div>
</template>
<script>
import Navbar from './components/Navbar.vue';
export default {
name: 'App',
components: {
Navbar
}
}
</script>
In the Navbar.vue, I have a method:
methods: {
getLoggedStatus(){
console.log('asdasd')
}
}
Lastly, I have a Login.vue that is loaded there on the router-view. I wanna acess the getLoggedStatus() from the Navbar.vue within Login.vue. How can I achieve this?
I tried putting a ref to the Navbar tag in App.vue:
<Navbar ref="navvy"/>
And calling in on the Login.vue with:
this.$refs.navvy.getLoggedStatus()
But it doesn't work.
A ref only works on children. You're rendering <Navbar> within app, so you cannot call that ref from login. You can only access this.$refs.navvy from App.vue.
There are several solutions for your problem.
Emit an event from Login to App, so App calls the method from a ref.
You can set a listener in the router-view, as:
<router-view #loggedStatus="callLoggedStatus" />
In your login, when you would want to call the navbar getLoggedStatus, you would instead emit that event:
this.$emit('loggedStatus')
And then in App.vue, you would defined a callLoggedStatus methods that call the ref:
callLoggedStatus() {
this.$refs.navvy.getLoggedStatus();
}
Given that you add the ref to the <Navbar> component in the APP template.
This solution is arguably the most similar to your proposed code, but I think it is a mess and you should avoid it, since you can end up listening to a lot of different events in your App.vue.
Use Vuex
I don't exactly know what getLoggedStatus does, but if you want to change how your navbar behaves when the user is logged in, you should probably setup a vuex store, so you register there wether the user is logged or not. Then in your navbar component you render things conditionally depending upon the user is logged or not.
#Lana's answer follows this idea, and is probably the closest to the official way to thins in Vue.
Use an event emitter
If you want to directly communicate between components that are not in the same family, I think an event emitter is a reasonable choice. You could setup an application wide event emitter after creating the app:
const app = new Vue({...});
window.emitter = new Vue();
(in the example we use a new Vue as event emitter. There is also the 'events' module which allow to use a EventEmitter)
And then any component can use it to send messages, so Login could do:
window.emitter.$emit('user-logged', myCustomPayload);
And Navbar on the other hand could do:
window.emitter.$on('user-logged', function() {
this.getLoggedStatus();
})
This last option is not well considered in the Vue community -Vuex is preferred- but for small applications I think it is the simplest.
The dirty hack
You can always export your component to window. In the Navbar created hook you could do:
created() {
window.Navbar = this;
}
And then in Login.vue you could, at any time:
window.Navbar.getLoggedStatus()
And it will work. However, this is surely an anti pattern and can cause a lot of harm to your project maintainability if you start doing this with several components.
It looks like getLoggedStatus returns some global state of the app. Use Vuex for managing these global variables.
Create a store like this:
// Make sure to call Vue.use(Vuex) first if using a module system
const store = new Vuex.Store({
state: {
loggedStatus: 0
},
getters: {
getLoggedStatus: state => {
// to compute derived state based on store state
return state.loggedStatus
}
}
})
Use store.state.loggedStatus in any Vue-component to access the global state or use getters like store.getters.getLoggedStatus if you need to compute derived state based on store state.
Related
I have made an example in Codesandbox
Is there a way to collapse the sidebar by clicking on the button 'Col-2'. The button 'Col-1' is working fine, but it's necessary that is works by clicking on 'Col-2'.
I've tried to call the collapsedButton but this didn't work.
<script>
export default {
name: 'template',
methods: {
setCollapsed () {
this.collapsed = !this.collapsed
},
collapseButton () {
this.$emit('setCollapsed')
this.collapsed = !this.collapsed
}
}
}
</script>
Can someone help me out with this?
You should make collapsed a prop and control it from the parent component. You'll need to listen for the event (this.$emit('setCollapsed')) in the parent component and update collapsed accordingly.
I hope this helps, good luck!
The recommended way to achieve this is to use a store.
The store is a module, external to any component, which can be imported in any number of components. When the state of the store (which is pretty much like the data function of a component) is changed, the change is propagated in every single component where the store is used.
Since you're using Vue 2, you could use a mixin.
Here's a mixin using a basic version of a store, under the hood: https://codesandbox.io/s/vue-forked-tz1yox?file=/src/components/sidebar.vue
In more detail, it uses a reactive object which serves as shared state for sidebar's collapsed status while exposing a writable computed (collapsed) and a toggleSidebar method.
Using the mixin in any component is the equivalent of writing the writable computed and the method into the component.
Note: mixins don't play nice with TypeScript. But the above solution doesn't need a mixin. I only used one so I didn't have to write the same computed and same method in both components.
The key to it working is the shared/external reactive object. In Vue 3 you would achieve this using Composition API.
ive seen afew answers that sort of answer my question but not fully, so let me explain what I want to do.
We use a global #app div within the layout of our website, which is a Laravel project. So all pages will be the same main Vue instance, due to this i'm separating key functionality into components.
So, the first example is just a simple Tab component, this either separates any children into tabs, or accepts some data which the single child component then renders.
So below i'm injecting some data from another component, this ajax component literally just does an ajax call, and makes the data available within it's slot.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
#{{ row['example' }}
</div>
</div>
</tabs>
</div>
</ajax>
Now this is all well and good, to a point, but this falls down with the below code. This contains a component which will allow the used to drag and drop elements, it re-arranges them by literally moving the data around and letting Vue handle the DOM changes.
This will of course work fine within it's own data which you have injected in, but when you change the data within the component below this then clears this child component.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
<draggable :injected="row">
<div>
<div v-for="item">
#{{ item }}
</div>
</div>
</draggable>
</div>
</div>
</tabs>
</div>
</ajax>
I need to find a way to make any changes to this data apply to the parent data, rather than the data passed into the child components.
What is the best practice to do this!?
Edit 1
Basically, I need any child component's manipulate the data within the ajax component. The children within ajax could change, or there could be more, so I just need them all to do this without knowing what order or where they are.
It is hard to come up with specifics on this one, but I am going to try to put you in the right direction. There are three ways to share data between components.
1) Passing down data via props, emitting data up via custom events
The passing down of data via props is a one-way street between the parent and child components. Rerendering the parent component will also re-render the child and data will be reset to the original state. See VueJS: Change data within child component and update parent's data.
2) Using a global event-bus
Here you create an event bus and use this to emit the data to different components. All components can subscribe to updates from the event bus and update their local state accordingly. You initiate an event bus like this:
import Vue from 'vue';
export const EventBus = new Vue();
You send events like this:
import { EventBus } from './eventbus.js'
EventBus.$emit('myAwsomeEvent', payload)
And you subscribe to events like this:
import { EventBus } from './eventbus.js'
EventBus.$on('myAwsomeEvent', () => {
console.log('event received)
})
You still need to manage state in the components individually. This is a good start with an Event bus: https://alligator.io/vuejs/global-event-bus/
3) Using Vuex
Using Vuex extracts the component state into the Vuex store. Here you can store global state and mutate this state by committing mutations. You can even do this asynchonously by using actions. I think this is what you need, because your global state is external to any components you might use.
export const state = () => ({
resultOfAjaxCall: {}
})
export const mutations = {
updateAjax (state, payload) {
state.resultOfAjaxCall = payload
}
}
export const actions= {
callAjax ({commit}) {
const ajax = awaitAjax
commit('updateAjax', ajax)
}
}
Using vuex you keep your ajax results separated from your components structure. You can then populate your state with the ajax results and mutate the state from your individual components. This way, it doesn't matter whether you recall ajax, or destroy components since the state will always be there. I think this is what you need. More info on Vuex here: https://vuex.vuejs.org/
I am searching for the best approach to change Vue variable value outside of component. I'm using Vue webpack as well.
I have created a project using vue webpack.
Inside its default App.vue file, I have a variable. For example, let's take showModal and its default value is false.
Then I built it in a single javascript file.
<button>Register</button> {{-- event button --}}
<div id="guest"></div> {{-- Here I'm rendering my template --}}
<script src="{{ asset('js/vue-js/guest.js') }}"></script> {{-- builded Javascript file --}}
And the problem is that I want to change my showModal variable to true, but the event button it is not on my component template.
What is the best approach to accomplish this?
If you want to access a vue component outside of vue you could register it to the window object and access it then from anywhere.
window.myVueComponent = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Now you can access it from anywhere else with
window.myVueComponent.myValue = 123
But this "style" is called global namespace pollution for reasons.
;)
I think it is better to extend your vue app so that the button is also within the vue-handled components.
Firstly, best approach wise it's prevalent to think about the relationships between your existing components and their relationships. So for instance if the information your trying to pass will be used in a direct sibling or further down the chain you could choose props.
If your dealing with two components that share no direct relationship other than there current state you will need to extrapolate to either using the repository pattern or Vuex (flux like state management library) where we can then pass a reference to state or into properties in the repository pattern.
FooRepository.js
export default class FooRepository {
SomeRef
ManyRef = []
addRef(name) {
this.someRef = name;
}
addRefs(names){
this.ManyRef.push(names);
}
}
The above can be instantiated in your App Layer and shared between your components using an instance property https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html
Dependent on your apps size it might be time to include Vuex where we can save a reference directly into our state and use it in a simmilar manner as the repo pattern. Though as it's an officially supported package the setup and use is much simpler:
const store = new Vuex.Store({
state: {
ref: null
},
mutations: {
saveRef (state, compRef) {
state.ref = compRef
}
}
})
This is a very basic store that shows how we could save a reference into it, we can then access this reference in our components once we've registered the store inside main. This can be done using this.$store.state.ref. These two approaches are considered the best approach over simple simple props and or something like the event emitter for components that share no direct relationship.
Create a new Vue instance just for emitting, call it global event manager.
global.event = new Vue()
when you want to emit an event ( like modal )
global.event.$emit('close-modal', (optional msg))
when you want to close modal :
// In your component
created(){
var App = this;
global.event.$on('close-modal', (optional msg)=>{
this.showModal = false;
})
}
Similarly do it for opening the modal. If you are using the vue CDN (normal js file of vue), instead of global.event use window.event while creating and only event while using. In browser if a variable which is undeclared is used then it refers to the window object.
This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0
I'm rewriting a small app to try and better understand React. I'm trying to determine the "correct"/most efficient method of sharing "singleton" data - for example, a user who's been properly authenticated upon login.
Right now the parent "application" component has a user property in its state, which I pass to child components as a prop:
<Toolbar user={this.state.user} />
<RouteHandler user={this.state.user}/>
(I'm using react-router). This works, and in read-only cases like this, isn't terrible. However, my actual login form component (which is a route, and would be inside RouteHandler), needs some way to "set" the new user data, so I also need to pass in some callback:
<RouteHandler onAuthenticated={this.setUser} user={this.state.user}/>
Not a big problem, except for the fact that now this method is available to every "route" handled by RouteHandler.
I've been reading up and it seems like the only alternative is an EventEmitter or Dispatch-style system.
Is there a better way I'm missing? Is an event emitter/dispatcher system worth using when there's really only one or two uses in an app this small?
React Context provides a way to pass data through the component tree without having to pass props down manually at every level. With context, every component nested under a Provider has access to the data, but you need to explicitly read the value.
I recommend using React Hooks with useContext. One way to do this would be to set the value of the context to be an object with setter and getter functions.
import React, { useState, useContext } from "react"
export const UserContext = React.createContext({}); //Initialise
//Wrapper with getter and setter
const App = () => {
const [user, setUser] = useState();
const value = {user, setUser}
return (
<div>
<UserContext.Provider value={value}>
<RouteHandler/>
<AnotherComponent/>
</UserContext>
<ComponentWithoutAccessToUserContext/>
</div>
)
}
const RouteHandler = (props)=> {
const { user, setUser } = useContext(UserContext)
// This component now has access to read 'user' and modify it with 'setUser'
}
const AnotherComponent = () => {
return (<div>Component which could be get access to the UserContext</div>)
}
For singleton - you can just create separate module for user service and import it into module where you define components that need it it.
Other quite similar, but more powerful option, is to use DI container - define your react components as a services in DI container, with dependencies to other services like one for user data. This would be more suitable for universal(isomorphic) app - because, you will be able to easily replace dependencies with specific implementations, or for case when you need to create separate instances for separate scopes(like for user sessions server-side).
Also if using this approach, I would recommend to separate pure react components from logic - you can create separate pure component that receives all data, and callbacks as a props, and than create HoC component in DI container that will wrap it and will pass needed data and callbacks.
If you need DI container - there is a plenty of them, but I will recommend to look at angular 2 di container, or if you would like something simpler - below I referenced my project, it has very simple but yet powerful DI inspired by angular 2 DI(it is easy to pull from that project - just one file + test)).
About notifying components about changes, and organising async logic - you still will need something like EventEmitter to notify components about changes, and you will need to write life cycle callbacks for components to subscribe/unsubscribe from updates… You can do this by hand or creating mixin or HoC to shorten that.
But from my perspective, there is better approach - try reactive programming, and RxJS in particular. It plays very well with react.
If you are interested about options connecting Rx with React - take a look at gist https://gist.github.com/zxbodya/20c63681d45a049df3fc, also it can be helpful about implementing HoC component with subscription to EventEmitter mentioned above.
I have a project that is intended for creating isomorphic(rendered server side, and than same html reused client side) widgets with react.
It has DI container to pass dependencies, and it uses RxJS to manage async logic:
https://github.com/zxbodya/reactive-widgets
One way is to subscribe to an Observable emitted from your data model.
Router.run(routes, Handler =>
Model.subject.subscribe(appState =>
React.render(
<Handler {...appState}/>,
document.getElementById('app')
)
)
);
...appState being the data coming from observable (in this case model), making these your props so you can then feed them to the app like below
<RouteHandler {...this.props} />
and any child component can pick them up with this.props
the answer is more complex that this but if you look at RxJS+React you will get a full working examples of simple data flows