Ionic 5, Vue 3 - How to use Ionic lifecycle hooks inside setup()? - vue.js

The Ionic documentation describes how to use the Ionic lifecycle methods like ionViewWillEnter, ionViewDidEnter etc. inside Vue method.
https://ionicframework.com/docs/vue/lifecycle
I'm looking for a way to access them inside the new Vue 3 setup() method so that I can able to access the properties defined there. Is it something possible?
export default defineComponent({
...
setup(){
const list = ref([]);
// I need something like this
const ionViewDidEnter = () => {
list.value.push(...['some', 'array', 'here']);
},
return {
list,
ionViewDidEnter
};
}
});

This is now possible in a composition API style since this PR was merged
https://github.com/ionic-team/ionic-framework/pull/22970
export default defineComponent({
...,
components: { IonPage },
setup() {
onIonViewDidEnter(() => {
console.log('ionViewDidEnter!');
});
}
});

Unfortunately no, at least not yet, since the code firing the events is specifically looking for the lifecycle events to be registered as part of the component methods. Luckily I've submitted a PR which is going to fix this and allow you to define them the same way you would define mounted or any other Vue hooks.
I will make sure to add tests that cover the composition API scenario as well.
https://github.com/ionic-team/ionic-framework/pull/22241
Meaning that you'd be able to do it this way:
export default defineComponent({
...
ionViewDidEnter() {
...
},
setup(){
...
}
});

Just to add to what Mark Beech said, you will have to import onIonViewDidEnter
import { onIonViewDidEnter } from '#ionic/vue';
export default defineComponent({
...
ionViewDidEnter() {
console.log('Hompage');
},
setup(){
...
}
});

Related

How to `emit` event out of `setup` method in vue3?

I know I can call the emit method from the setup method, but is there any way to emit event from any other functions without passing the emit method from setup method(not the the functions in the methods option, but a useXXX function) ?
setup function takes two arguments, First one is props.
And the second one is context which exposes three component properties, attrs, slots and emit.
You can access emit from context like:
export default {
setup(props, context) {
context.emit('event');
},
};
or
export default {
setup(props, { emit }) {
emit('event');
},
};
Source
in vue3 typescript setup
<script setup lang="ts">
const emit = defineEmits()
emit('type', 'data')
<script>
20220626
<script setup lang="ts">
const emit = defineEmits(['emit_a', 'emit_b'])
emit('emit_a')
emit('emit_b', 'emit_b_data')
<script>
With Vue 3 setup syntax sugar
<script setup lang="ts">
import { defineEmits } from 'vue'
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
function yourFunction (id: number) {
emit('change', id)
}
<script>
See docs: https://v3.vuejs.org/api/sfc-script-setup.html#typescript-only-features
Here's the proper way to emit events programmatically (using javascript) in vue3:
export default defineComponent({
// See: https://vuejs.org/guide/components/events.html#declaring-emitted-events=
emits: 'myEventName', // <--- don't forget to declare custom events emitted
setup(_, { emit }) {
emit('myEventName') // <--- emit custom event programmatically whenever we want
},
})
The emits function can just as easily be passed as a param to any function not declared inside setup.
Side-note regarding other answers: we should avoid using getCurrentInstance(), which was intended for library authors needing access to internals of vue components (a.k.a. this of vue v2), when there are better alternatives. Especially when those alternatives were designed explicitly for our use case.
methods: {
minhaFuncao(){
let data = "conteudo";
this.$emit("nomeDoMEuEvento", data);
}
}
SEE MORE AT :https://github.com/Carlos-Alexandre-Leutz/emitir-eventos-filho-pra-pai-com-dados-no-vue3
export const useEmit = () => {
const vm = getCurrentInstance()
const emitFactory = (event: string) => (...args: any[]) => vm.emit(event, ...args)
return {
emit: vm.emit,
emitModel: emitFactory('update:modelValue')
}
}
const useButtonHandlers = () => {
const { emit } = useEmit()
const onClick = () => emit('click')
return {
onClick
}
}
You can use getCurrentInstance from Vue. You can check it out in the docs.
Usage is like
function useFunctionThatEmitsSomething(){
const instance = getCurrentInstance();
// do something
instance.emit('event');
}
Edit: Even though this answer solves the author's problem, as per the linked docs, this method is intended only for ADVANCED use cases, e.g authoring a plugin or library. For common use cases, like building a simple SPA, using this is TOTALLY DISCOURAGED and should be avoided at all costs, since it can lead to unreadable and unmaintenable code. If you feel the need to use this in a case like that, you're probably doing something wrong.

Is there any way to pass mixin to component loaded through Vue Router

I have a mixin which contains beforeCreate lifecycle event.
I would like to import that mixin only into certain components, which are directly loaded through router. I don't want to go into each one of them and manually import the mixin, and I would also want to avoid loading it globally.
I believe that the proper way to do it is in route options, possibly overriding the component method, or by adding mixin option for the route (alongside props, meta...).
I requested this new feature, but I guess I was misunderstood, or I didn't understand the proposed solution.
I tried to create main Vue instance and extend it in my components, but the method only executed from the main component.
Is there any way to make this work?
Example of project code is here
Perhaps I've misunderstood what you're asking but I'd have thought you could achieve this by extending the component:
import Vue from 'vue'
import Router from 'vue-router'
import MyMixin from './mixins/MyMixin'
import MyList from './components/MyList'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/list',
name: 'list',
component: {
extends: MyList,
mixins: [MyMixin]
}
}
// ...
]
})
So rather than using MyList directly it's being extended and the mixin added in.
Or if you've got a lot of them and want to avoid duplication you could do something like this:
export default new Router({
routes: [
{
path: '/list',
name: 'list',
doMagic: true,
component: MyList
}
// ...
].map(route => {
if (route.doMagic) {
route.component = {
extends: route.component,
mixins: [MyMixin]
}
}
return route
})
})
Here I've used a flag called doMagic to determine which components to modify but if you just wanted to change all of them then you wouldn't need such a flag.
That doesn't take nested routes into account but it could be adapted if required.
Likewise if you're using async components then you'll have to fiddle around with the promises but the core principle should be exactly the same.
Update:
Based on the example code provided, the following seems to work with lazily loaded components:
const routes = [
// ... routes defined as usual
];
const newRoutes = routes.map(route => {
const originalComponent = route.component;
let component = null;
if (typeof originalComponent === 'object') {
// Components that aren't lazily loaded
component = wrap(originalComponent);
} else {
// Components that are lazily loaded
component = async () => {
const module = await originalComponent();
return wrap(module.default || module);
}
}
return {
...route,
component
};
function wrap (cmp) {
return {
extends: cmp,
mixins: [MyMixin]
}
}
});
export default new Router({
routes: newRoutes
});

Decentralizing functions in vuejs

Am from Angular2 whereby i was used to services and injection of services hence reusing functions how do i achieve the same in vuejs
eg:
I would like to create only one function to set and retrieve localstorage data.
so am doing it this way:
In my Login Component
this.$axios.post('login')
.then((res)=>{
localstorage.setItem('access-token', res.data.access_token);
})
Now in another component when sending a post request
export default{
methods:{
getvals(){
localstorage.getItem('access-token') //do stuff after retrieve
}
}
}
Thats just one example, Imagine what could happen when setting multiple localstorage items when retrieving one can type the wrong key.
How can i centralize functionality eg: setting token(in angular2 would be services)
There are a few different ways to share functionality between components in Vue, but I believe the most commonly used are either mixins or custom modules.
Mixins
Mixins are a way to define reusable functionality that can be injected into the component utilizing the mixin. Below is a simple example from the official Vue documentation:
// define a mixin object
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
Custom module
If there are a lot of shared functionality with a logical grouping it might make sense to instead create a custom module, and import that where you need it (like how you inject a service in angular).
// localStorageHandler.js
const localStorageHandler = {
setToken (token) {
localStorage.setItem('access-token', token)
},
getToken () {
localstorage.getItem('access-token')
}
}
export default localStorageHandler
And then in your component:
// yourcomponent.vue
import localStorageHandler from 'localStorageHandler'
export default{
methods:{
getvals(){
const token = localStorageHandler.getToken()
}
}
}
Modules are using the more modern syntax of JavaScript, which is not supported in all browsers, hence require you to preprocess your code. If you are using the vue-cli webpack template it should work out of the box.

vue bestpractice api handling

Been reading the docs and googling around for best practice to handle api calls in bigger projects without luck (or ateast not what Im searching for).
I want to create a service / facade for the backend that I can load in every component that needs it. For exampel.
I want to fetch historical data for weather in a service so in every component I need this I can just load the weather-serivce and use a getter to fetch the wanted data. I would like to end up with something like below. But I dosent get it to work. So I wonder, what is best practice for this in vue.js?
import WeatherFacade from './data/WeatherFacade.vue'
export default {
name: 'Chart',
created () {
console.log(WeatherFacade.getWeather())
},
components: {
WeatherFacade
}
}
ps. using vue 2.1.10
It could be easily done by creating some external object that will hold those data and module bundling.What I usually do in my projects is that I create services directory and group them in order I want.
Let's break it down - services/WeatherFascade.js (using VueResource)
import Vue from 'vue'
export default {
getWeather() {
return Vue.http.get('api/weather')
}
}
If you have to pass some dynamic data such as ID, pass it as just parameter
import Vue from 'vue'
export default {
getWeather(id) {
return Vue.http.get(`api/weather/${id}`)
}
}
Then in your component you can import this service, pass parameters (if you have them) and got data back.
import WeatherFascade from '../services/WeatherFascade'
export default {
data() {
return {
weatherItems: []
}
},
created() {
this.getWeatherData()
},
methods: {
getWeatherData() {
WeatherFascade.getWather(// you can pass params here)
.then(response => this.weatherItems = response.data)
.catch(error => console.log(error))
}
}
}
You can use any library for that you like, for instance axios is cool.

Inject Custom Store into Application

Vuex allows you to inject the store into your root instance, making it accessible via this.$store in all child components.
Without Vuex, is it possible to inject a custom store implementation into child components?
e.g.
// main.js
let app = new Vue({router, store, ...App}).$mount("#flightdeck-app")
export { app, store, router }
// SomeComponent.vue
export default {
name: "Overview",
components: { "credentials": Credentials },
computed: {
count() {
// injected store; is currently undefined.
return this.$store.state.items.length
}
},
Attempting to access this.$store results in undefined in child components, as Vuex seemingly has additional hooks to make this happen.
You may create a custom plugin for vue that register an initialize your custom store (or anything, I'll create a logger object just for demonstration).
For example you could have
//myLogger.js
export default {
install(Vue, options) {
function log(type, title, text) {
console.log(`[${type}] ${title} - ${text}`);
}
Vue.prototype.$log = {
error(title, text) { log('danger', title, text) },
success(title, text) { log('success', title, text) },
log
}
}
}
Before your main Vue instance tell to register your plugin
//main.js
import Logger from './path/to/myLogger';
Vue.use(Logger);
var vm = new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
Now you can call this.$log on any child component
//myComponent.vue
export default {
data() {
return {};
},
methods: {
Save() {
this.$log.success('Transaction saved!');
}
}
}
Hope it helps, for more detail please see Vue plugins documentation
Just use in your components files:
import store from './vuex/store.js'
Place your store in separate file to get it clear.
Import store to every component where you need store.