Vuejs setting up event bus - events

So in my root app.js i have
window.Vue = require('vue');
const EventBus = new Vue()
Object.defineProperties(Vue.prototype, {
$bus: {
get: function () {
return EventBus
}
}
})
const app = new Vue({
el: '#backend',
EventBus,
components: {
FirstComponent
}
});
Now in the first component
clickbtn(){
this.$bus.$emit('test', { "testval":"setting up event bus" })
}
components:{
ChildComponent //local component
}
Now on the child component
created(){
this.$bus.$on('test', ($event) => {
console.log('Test event triggered', $event)
})
}
Where am i going wrong in the setup since even console.log(this) doesnt have $bus in it.
I was following This to setup
I still would like to use $bus as it looks good and abit organized.How do i make it happen.

I usually do a separation with the EventBus.
eventbus.js
import Vue from 'vue';
export const EventBus = new Vue();
Then i simply do an import in every component that needs to listen for event. On bigger projects I would even create a events.js and eventListener.js file and then handle everything there.
With complete separation
eventbus.js
This will be our event bus and is used from all other places.
import Vue from 'vue';
export const EventBus = new Vue();
event.js
This file is basically a library of common events. This makes it easier to maintain.
import { EventBus } from './Eventbus.js';
import { Store } from './Store.js'; // If needed
class Event {
// Simple event
static showMessage(message) {
EventBus.$emit('showMessage', message);
}
}
eventlistener.js
Event listener for our common events. Again this makes it easier to maintain. This could be in the same event file, but I like the separation.
import { EventBus } from './Eventbus.js';
class EventListener {
// Simple event listener
static showMessage() {
EventBus.$on('showMessage', function() {
alert(message);
});
}
// Simple event listener with callback
static showMessage(callbackFunction) {
EventBus.$on('showMessage', callbackFunction);
}
}
ComponentA.vue
A random component. Imports the EventBus and Event collection as it is used somewhere in the vue component.
<template>
*SOME HTML*
</template>
<script>
import { Event } from 'event.js'
import { EventBus } from 'eventbus.js';
export default {
methods: {
throwAlert: function() {
Event.showMessage('This is my alert message');
}
}
}
</script>
ComponentB.vue
A random component. Imports the EventBus and EventListener collection as it is suppose to react on events on the eventbus.
<template>
*SOME HTML*
</template>
<script>
import { EventListener } from 'eventlistener.js'
import { EventBus } from 'eventbus.js';
export default {
mounted() {
// Will start listen for the event 'showMessage' and fire attached function defined in eventlistener.js
EventListener.showMessage();
// Will start listen for the event 'showMessage' and execute the function given as the 'callbackFunction' parameter. This will allow you to react on the same event with different logic in various vue files.
EventListener.showMessage(function(message) {
alert(message);
});
}
}
</script>

Related

Possible memory leak in NativeScript app if user reopens his app multiple times

I'm not sure where is the bug, maybe I'm using rxjs in a wrong way. ngDestroy is not working to unsubscribe observables in NativeScript if you want to close and back to your app. I tried to work with takeUntil, but with the same results. If the user close/open the app many times, it can cause a memory leak (if I understand the mobile environment correctly). Any ideas? This code below it's only a demo. I need to use users$ in many places in my app.
Tested with Android sdk emulator and on real device.
AppComponent
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Subscription, Observable } from 'rxjs';
import { AppService } from './app.service';
import { AuthenticationService } from './authentication.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnDestroy, OnInit {
public user$: Observable<any>;
private subscriptions: Subscription[] = [];
constructor(private appService: AppService, private authenticationService: AuthenticationService) {}
public ngOnInit(): void {
this.user$ = this.authenticationService.user$;
this.subscriptions.push(
this.authenticationService.user$.subscribe((user: any) => {
console.log('user', !!user);
})
);
}
public ngOnDestroy(): void {
if (this.subscriptions) {
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
}
}
async signIn() {
await this.appService.signIn();
}
async signOut() {
await this.appService.signOut();
}
}
AuthenticationService
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { AppService } from './app.service';
#Injectable({
providedIn: 'root',
})
export class AuthenticationService {
public user$: Observable<any>;
constructor(private appService: AppService) {
this.user$ = this.appService.authState().pipe(shareReplay(1)); // I'm using this.users$ in many places in my app, so I need to use sharereplay
}
}
AppService
import { Injectable, NgZone } from '#angular/core';
import { addAuthStateListener, login, LoginType, logout, User } from 'nativescript-plugin-firebase';
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
const user$ = new BehaviorSubject<User>(null);
#Injectable({
providedIn: 'root',
})
export class AppService {
constructor(private ngZone: NgZone) {
addAuthStateListener({
onAuthStateChanged: ({ user }) => {
this.ngZone.run(() => {
user$.next(user);
});
},
});
}
public authState(): Observable<User> {
return user$.asObservable().pipe(distinctUntilChanged());
}
async signIn() {
return await login({ type: LoginType.PASSWORD, passwordOptions: { email: 'xxx', password: 'xxx' } }).catch(
(error: string) => {
throw {
message: error,
};
}
);
}
signOut() {
logout();
}
}
ngOnDestroy is called whenever a component is destroyed (following regular Angular workflow). If you have navigated forward in your app, previous views would still exist and would be unlikely to be destroyed.
If you are seeing multiple ngOnInit without any ngOnDestroy, then you have instantiated multiple components through some navigation, unrelated to your subscriptions. You should not expect the same instance of your component to be reused once ngOnDestroy has been called, so having a push to a Subscription[] array will only ever have one object.
If you are terminating the app (i.e. force quit swipe away), the whole JavaScript context is thrown out and memory is cleaned up. You won't run the risk of leaking outside of your app's context.
Incidentally, you're complicating your subscription tracking (and not just in the way that I described above about only ever having one pushed). A Subscription is an object that can have other Subscription objects attached for termination at the same time.
const subscription: Subscription = new Subscription();
subscription.add(interval(100).subscribe((n: number) => console.log(`first sub`));
subscription.add(interval(200).subscribe((n: number) => console.log(`second sub`));
subscription.add(interval(300).subscribe((n: number) => console.log(`third sub`));
timer(5000).subscribe(() => subscription.unsubscribe()); // terminates all added subscriptions
Be careful to add the subscribe call directly in .add and not with a closure. Annoyingly, this is exactly the same function call to make when you want to add a completion block to your subscription, passing a block instead:
subscription.add(() => console.log(`everybody's done.`));
One way to detect when the view comes from the background is to set callbacks on the router outlet (in angular will be)
<page-router-outlet
(loaded)="outletLoaded($event)"
(unloaded)="outletUnLoaded($event)"></page-router-outlet>
Then you cn use outletLoaded(args: EventData) {} to initialise your code
respectively outletUnLoaded to destroy your subscriptions.
This is helpful in cases where you have access to the router outlet (in App Component for instance)
In case when you are somewhere inside the navigation tree you can listen for suspend event
Application.on(Application.suspendEvent, (data: EventData) => {
this.backFromBackground = true;
});
Then when opening the app if the flag is true it will give you a hint that you are coming from the background rather than opening for the first time.
It works pretty well for me.
Hope that help you as well.

How to set value in component from ajax request to parameters during creation in Vue?

I am learning Vue and trying to set one value in component during its creation from one Ajax request.
Here is the structure of src folder:
src
assets
components
Management.vue
router
index.js
vuex
modules
caseSuiteList.js
index.js
actions.js
getters.js
App.vue
main.js
Management.vue:
<template>
</template>
<script>
import {mapState} from 'vuex'
import store from 'vuex'
export default {
name: "Management",
computed: mapState({
suite_list: state => state.caseSuiteList.suite_list
}),
created(){
store.dispatch('refresh')
}
}
</script>
caseSuiteList.js:
import axios from 'axios'
const state = {
suite_list : {}
}
const mutations = {
refreshSuiteList(state, payload) {
state.suite_list = payload
}
}
const actions = {
refresh (context) {
axios.get('http://127.0.0.1:8601/haha').then(function(res){
console.log(res.data);
context.commit('refreshSuiteList', res.data);
}
});
}
}
export default {
state,
mutations,
actions
}
How to dispatch action of caseSuiteList.js in created() of Management.vue to make this happen?
Within the vue instance (or any component within) you access the store with this.$store.
Try dispatching in the created hook of your component.
created() {
this.$store.dispatch('refresh');
}
More information about Vuex: https://vuex.vuejs.org/guide/actions.html

How to call component method from app event in nativescript

How do we update component data, inside a app event? this.matches = x gets ignored
import * as app from "tns-core-modules/application";
export class HomeComponent implements OnInit {
matches; // How to refresh this??
constructor() {
app.on(app.resumeEvent, (args: app.ApplicationEventData) => {
// how to change matches here??
});
}
}
You have to run your code inside NgZone as resume event will be triggered outside Angular's context.
constructor(ngZone: NgZone) {
app.on(app.resumeEvent, (args: app.ApplicationEventData) => {
ngZone.run(() => {
// Update here
});
});
}

Vue.js event emitting from child component to (grand)parent component using global eventbus

I want to use a global eventbus to emit events from a child up to a (grand)parent.
In My main.js: I make a global eventbus available to all components.
import Vue from 'vue'
import App from './App'
const eventHub = new Vue()
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
Vue.mixin({
data: function () {
return {
eventHub: eventHub
}
}
})
Then, in my Childcomponent.vue: I emit an event to the eventbus on a click event
<template>
<button #click="save">Save</button>
</template>
<script>
let data = {
columnName: '',
order: 0
}
export default {
...
name: 'init-column',
methods: {
save: function () {
this.eventHub.$emit('newColumn', data)
}
}
...
}
</script>
Then, in a Parentcomponent.vue I want to catch this event and do something with the data that the child had transmitted:
<template>
<div id="app">
<column v-for="column in allData"></column>
<init-column v-if="newColumn"></init-column>
</div>
</div>
</template>
<script>
import initColumn from './components/Init-column'
let newColumn = false
export default {
...
name: 'project',
ready: function () {
this.eventHub.$on('newColumn', (event) => {
console.log(event)
})
}
...
}
</script>
I'm not sure where to put the $on listener, I saw examples where they put $on in the ready hook. The code above does nothing, however I get no error in the console.
The ability to do this goes away with Vue 3. The RFC below mentions the motivation and links to some issues for further help.
https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md
I don't think data is the right place for the event bus. I definitely wouldn't use a global mixin for it either.
What I've done in the past is have a simple bus.js file like:
import Vue from 'vue'
export default new Vue()
Then, in any component that needs the bus I just
import bus from './bus.js'
Then I normally do this to emit events.
bus.$emit('foo', whatever)
and this to catch them
created () {
bus.$on('foo', this.someMethod)
}
I prefer to do it in created since that's the earliest step in the lifecycle you can do this.
Also, this issue on github has some very nice examples: https://github.com/vuejs/vuejs.org/pull/435
I got the desired effect with a custom event: #newColumn="event"
<init-column #newColumn="event" v-if="newColumn"></init-column>
...
methods: { event: function(e) { console.log(e) }
So whenever I $emit from the child it does call the event method.
This works well, but for some strange reason the listener $on does not. Maybe I am missing something with the $on method.
You can put the $on listener in the created hook as specified in the docs: https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
You cannot use ready hook in Vue 2.0. ready hook was originally available in Vue 1.x, but now deprecated.

Loading component data asynchronously server side with Mobx

I'm having an issue figuring out how to have a react component have an initial state based on asynchronously fetched data.
MyComponent fetches data from an API and sets its internal data property through a Mobx action.
Client side, componentDidMount gets called and data is fetched then set and is properly rendered.
import React from 'react';
import { observer } from 'mobx-react';
import { observable, runInAction } from 'mobx';
#observer
export default class MyComponent extends React.Component {
#observable data = [];
async fetchData () {
loadData()
.then(results => {
runInAction( () => {
this.data = results;
});
});
}
componentDidMount () {
this.fetchData();
}
render () {
// Render this.data
}
}
I understand that on the server, componentDidMount is not called.
I have something like this for my server:
import React from 'react';
import { renderToString } from 'react-dom/server';
import { useStaticRendering } from 'mobx-react';
import { match, RouterContext } from 'react-router';
import { renderStatic } from 'glamor/server'
import routes from './shared/routes';
useStaticRendering(true);
app.get('*', (req, res) => {
match({ routes: routes, location: req.url }, (err, redirect, props) => {
if (err) {
console.log('Error', err);
res.status(500).send(err);
}
else if (redirect) {
res.redirect(302, redirect.pathname + redirect.search);
}
else if (props) {
const { html, css, ids } = renderStatic(() => renderToString(<RouterContext { ...props }/>));
res.render('../build/index', {
html,
css
});
}
else {
res.status(404).send('Not found');
}
})
})
I have seen many posts where an initial store is computed and passed through a Provider component. My components are rendered, but their state is not initialized. I do not want to persist this data in a store and want it to be locally scope to a component. How can it be done ?
For server side rendering you need to fetch your data first, then render. Components don't have a lifecycle during SSR, there are just render to a string once, but cannot respond to any future change.
Since your datafetch method is async, it means that it cannot ever affect the output, since the component will already have been written. So the answer is to fetch data first, then mount and render components, without using any async mechanism (promises, async etc) in between. I think separating UI and data fetch logic is a good practice for many reasons (SSR, Routing, Testing), see this blog.
Another approach is to create the component tree, but wait with serializing until all your promises have settled. That is the approach that for example mobx-server-wait uses.

Resources