What is the benefit to use useNuxtApp() on Nuxt3? - vuejs3

I'd like to know about new Nuxt3 feature called useNuxtApp.
Official document says, to use provide, you can do like below.
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
console.log(nuxtApp.$hello('name')) // Prints "Hello name!"
However it seems like you can also still use provide/inject.
For instance, I define the method 'hello' on parent component, then I also want to use it on child component, I can provide 'hello' for child from parent component and inject it.
You can still do same things by using provide/inject, so does anyone know what is the benefit using useNuxtApp?? And what is the difference between provide/inject and useNuxtApp except for syntax??

useNuxtApp built-in composable of Nuxt3
It is used to access shared runtime context of Nuxt available in server / client side, like: Vue App Instance, Runtime Hooks, Runtime Config Variables, Internal States etc.
Example:
i) ssrContext,
ii) payload,
iii) helper methods etc.
Values getting by this composable will be available across all composables, components, plugins (all files of .vue)
In Nuxt 2, this was referred to as "Nuxt Context"
Let's see an example how it can be use in Nuxt Plugin and by extending the plugin how it will give us a helper method and how we can use that helper method to get value to the rest of Nuxt Application.
say, we have a plugin in ~/plugins/my-plugin.js, where returning "provide" option as helper method.
in ~/plugins/my-plugin.js file
export default defineNuxtPlugin(() => {
...
return {
provide: {
hello: (msg: string) => `Hello ${msg}!`
}
}
...
})
in any template (pages, components or any .vue file of the Nuxt App)
<template>
<div>
{{ $hello('world') }}
</div>
</template>
<script setup>
const { $hello } = useNuxtApp()
</script>
// or
<script setup>
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
console.log(nuxtApp.$hello('name')) // Prints "Hello name!"
</script setup>

Related

nextjs links without strings

Im new to nextjs, and Im checking if it will be good for the app that will have pretty complex and messy internal navigation. Just checked their documentation and I see that they recommend usage
of Link component like this <Link href="/your_path">Path</Link>. A bit scary is that I have to provide 'your_path' as a string so every time i change page file name I have to manually update code that redirects to this page. Is there any solution that allows me to define routing on my own so I can write something like (pseudocode)
routes = [
...
{
page : 'page_name',
path : 'path_to_page'
}
...
]
So instead of using string I can do <Link href="{route.path}">Path</Link> or Im condemned to use this file-system based router with all consequences?
The simple answer is yes!
When you want to change a user route in NextJs you have 2 options,
The first is with the <Link> Element that you can specify a href to where it directs.
And you also have a useRouter hook for more complex routing for example if the user does an action that requires moving him into a different route you can do it internally in your handlers.
For more information about useRouter hook.
What I usually do is storing my routes in an object
const ROUTES = {
HOME: "/",
ABOUT: "/about"
}
and wherever you call routes you just use the object so F.E
With Link tag
<Link href={ROUTES.ABOUT}>ABOUT PAGE</Link>`
with useRouter hook
// Inside a React Component
const router = useRouter();
const handleNavigateToAbout = () => {
router.push(ROUTES.ABOUT);
}
return (
// SOME JSX
<button onClick={handleNavigateToAbout}> Go to about page! </button>
)

Using querySelector() in vue Compostion API

I'm new to Vue composition API and need to use the Document method querySelector. However, it is not working as expected. If i write
<nav class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = document.querySelector(".home-menu");
observer.observe(target);
console.log(target);
target is null. Reading docs I see the ref attribuute and if I
<nav ref="navbar" class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const target = ref("navbar");
console.log(target);
I do console an object. Is ref the way that you get a DOM element in composition API? Can I now use target in my observer object? Is it equivalent to querySelector? I tried this
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = ref("navbar");
observer.observe(target);
but got error
Uncaught TypeError: IntersectionObserver.observe: Argument 1 does not implement interface Element.
Thanks.
The reason document.querySelector is returning null is that the DOM does not contain that element yet.
The script setup runs when the component is created, so the component's DOM has not been created yet.
You can use the onMounted lifecycle hook to run code when the component has been mounted. I created a playground to demonstrate this.
From the Lifecycle docs:
For example, the onMounted hook can be used to run code after the component has finished the initial rendering and created the DOM nodes
There are then two approaches to achieve what you want. You can use continue to use querySelector, or you can use template refs.
Personally, I use template refs for static elements (such as navbars, etc.) and querySelector for dynamic selections.
Using Template Refs
A template ref is a regular Vue ref, but by connecting it to an element or child component via the ref attribute you can obtain a direct reference to that element or component.
If you connected it to an element, the value will be that element; if you connected it to a child component, the value will be that component's component instance; if nothing is connected to it, the value will be null.
Steps
Create a ref:
const navbar = ref(null);
This will be used to refer to the element.
Connect the template ref to the element by setting the ref attribute on the element to the name you used for the template ref:
<nav ref="navbar" ...>
Paraphrasing the Template Refs docs:
ref is a special attribute. It allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.
Connect the observer when the component is mounted:
onMounted(() => {
observer.observe(navbar.value);
})
Paraphrasing the docs again:
Note that you can only access the ref after the component is mounted. If you try to access navbar before then, it will be null. This is because the element doesn't exist until after the first render!
Optionally (see below), disconnect the observer when the component is being unmounted:
onBeforeUnmount(() => {
observer.disconnect();
})
Note that I don't believe this is technically necessary, as the observer should be garbage collected when the component is destroyed.
You can fiddle around with this experiment I did in the SFC playground, trying to create a memory leak.
Code Example
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const el = ref(null);
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
observer.observe(el.value)
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div ref="el">
I'm the target!
</div>
</template>
Using querySelector
Alternatively, you can still use querySelector. The same lifecycle considerations apply.
<nav class="home-menu ...">
onMounted(() => {
const target = document.querySelector(".home-menu");
observer.observe(target);
})
Code Example
<script setup>
import { onMounted, onUnmounted } from "vue";
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
const target = document.querySelector(".target");
observer.observe(target);
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div class="target">
I'm the target!
</div>
</template>
Other Docs
This is the diagram from the Lifecycle docs:
Side Notes
The reason console.log(target) did log an object is because a Vue ref is an object. The actual value is accessed via the value property.
The technical reason for this is that Vue can then detect when that property is accessed, and do its reactivity magic; including when it was a complete reassignment of the value.
You can use the ref approach, calling the variable with the same name you declared the ref in the template:
const navbar = ref(null);
However, you should await the component to be mounted to observe:
onMounted(() => {
observer.observe(target);
})
remember also to disconnect it when you unmount the component:
onBeforeUnmount(() => {
observer.disconnect();
})

Can Svelte be used for templating like Handlebars?

My objective is to let end-users build some customization in my app. Can I do something like this? I know this is sometimes also referred to as liquid templates, similar to how handlebars.js works.
app.svelte
<script>
let name = 'world';
const template = '<h1> Hello {name} </h1>'
</script>
{#html template}
I'm sorry if this is already answered, but I could not find it.
Link to REPL.
This is a little bit hacky but it will do the trick:
<script>
let name = 'world';
let template;
</script>
<div class="d-none" bind:this={template}>
<h1>Hello {name}</h1>
</div>
{#html template?.innerHTML}
<style>
.d-none {
display: none;
}
</style>
If the template should be a string one solution might be to simply replace the {variable} with the values before displaying via a {#html} element >> REPL
Notice the warning in the SVELTE docs
Svelte does not sanitize expressions before injecting HTML. If the data comes from an untrusted source, you must sanitize it, or you are exposing your users to an XSS vulnerability.
(Inside a component $$props could be used to get the passed values in one object if they were handled seperately)
<script>
const props = {
greeting: 'Hello',
name: 'world'
}
let template = '<h1> {greeting} {name} </h1>'
let filledTemplate = Object.entries(props).reduce((template, [key,value]) => {
return template.replaceAll(`{${key}}`, value)
},template)
</script>
{#html filledTemplate}
Previous solution without string
To achieve this I would build a Component for every template and use a <svelte:component> element and a switch to display the selected one > REPL
[App.svelte]
<script>
import Template1 from './Template1.svelte'
import Template2 from './Template2.svelte'
let selectedTemplate = 'template1'
const stringToComponent = (str) => {
switch(str) {
case 'template1':
return Template1
case 'template2':
return Template2
}
}
</script>
<button on:click={() => selectedTemplate = 'template1'}>Template1</button>
<button on:click={() => selectedTemplate = 'template2'}>Template2</button>
<svelte:component this={stringToComponent(selectedTemplate)} adjective={'nice'}/>
[Template.svelte]
<script>
export let adjective
</script>
<hr>
<h1>This is a {adjective} template</h1>
<hr>
Well, you could do it, but that not what Svelte was designed for.
Svelte was designed to compile the template at build time.
I'd recommend using a template engine (like handlebars) for your use case.
A. Using Handlebars inside Svelte REPL:
<script>
import Handlebars from 'handlebars';
let name = 'world';
const template = "<h1> Hello {{name}} </h1>";
$: renderTemplate = Handlebars.compile(template);
</script>
{#html renderTemplate({ name })}
This of course limits the available syntax to handlebars, and you can't use svelte components inside a handlebar template.
B. Dynamic Svelte syntax templates inside a Svelte app
To be able to use svelte syntax you'll need to run the svelte compiler inside the frontend.
The output the compiler generates is not directly usable so you'll also need to run a bundler or transformer that is able to import the svelte runtime dependencies. Note that this is a separate runtime so using <svelte:component> wouldn't behave as expected, and you need to mount the component as a new svelte app.
In short, you could, but unless you're building a REPL tool you shouldn't.
C. Honourable mentions
Allow user to write markdown, this gives some flexibility (including using html) and use marked in the frontend to convert it to html.
Write the string replacements manually {#html template.replace(/\{name\}/, name)}

Apply global variable to Vuejs

I have a javascript variable which I want to pass globally to Vue components upon instantiation thus either each registered component has it as a property or it can be accessed globally.
Note:: I need to set this global variable for vuejs as a READ ONLY property
Just Adding Instance Properties
vue2
For example, all components can access a global appName, you just write one line code:
Vue.prototype.$appName = 'My App'
Define that in your app.js file and IF you use the $ sign be sure to use it in your template as well: {{ $appName }}
vue3
app.config.globalProperties.$http = axios.create({ /* ... */ })
$ isn't magic, it's a convention Vue uses for properties that are available to all instances.
Alternatively, you can write a plugin that includes all global methods or properties. See the other answers as well and find the solution that suits best to your requirements (mixin, store, ...)
You can use a Global Mixin to affect every Vue instance. You can add data to this mixin, making a value/values available to all vue components.
To make that value Read Only, you can use the method described in this Stack Overflow answer.
Here is an example:
// This is a global mixin, it is applied to every vue instance.
// Mixins must be instantiated *before* your call to new Vue(...)
Vue.mixin({
data: function() {
return {
get globalReadOnlyProperty() {
return "Can't change me!";
}
}
}
})
Vue.component('child', {
template: "<div>In Child: {{globalReadOnlyProperty}}</div>"
});
new Vue({
el: '#app',
created: function() {
this.globalReadOnlyProperty = "This won't change it";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalReadOnlyProperty}}
<child></child>
</div>
In VueJS 3 with createApp() you can use app.config.globalProperties
Like this:
const app = createApp(App);
app.config.globalProperties.foo = 'bar';
app.use(store).use(router).mount('#app');
and call your variable like this:
app.component('child-component', {
mounted() {
console.log(this.foo) // 'bar'
}
})
doc: https://v3.vuejs.org/api/application-config.html#warnhandler
If your data is reactive, you may want to use VueX.
You can use mixin and change var in something like this.
// This is a global mixin, it is applied to every vue instance
Vue.mixin({
data: function() {
return {
globalVar:'global'
}
}
})
Vue.component('child', {
template: "<div>In Child: {{globalVar}}</div>"
});
new Vue({
el: '#app',
created: function() {
this.globalVar = "It's will change global var";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalVar}}
<child></child>
</div>
If the global variable should not be written to by anything, including Vuejs, you can use Object.freeze to freeze your object. Adding it to Vue's viewmodel won't unfreeze it.
Another option is to provide Vuejs with a frozen copy of the object, if the object is intended to be written globally but just not by Vue: var frozenCopy = Object.freeze(Object.assign({}, globalObject))
you can use Vuex to handle all your global data
In your main.js file, you have to import Vue like this :
import Vue from 'vue'
Then you have to declare your global variable in the main.js file like this :
Vue.prototype.$actionButton = 'Not Approved'
If you want to change the value of the global variable from another component, you can do it like this :
Vue.prototype.$actionButton = 'approved'
https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
If you’d like to use a variable in many components, but you don’t want to pollute the global scope. In these cases, you can make them available to each Vue instance by defining them on the Vue prototype:
Vue.prototype.$yourVariable = 'Your Variable'
Please remember to add this line before creating your Vue instance in your project entry point, most of time it's main.js
Now $yourVariable is available on all Vue instances, even before creation. If we run:
new Vue({
beforeCreate: function() {
console.log(this.$yourVariable)
}
})
Then "Your Variable" will be logged to the console!
doc: https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
If you want to make this variable immutable, you can use the static method Object.defineProperty():
Object.defineProperty(Vue.prototype, '$yourVariable', {
get() {
return "Your immutable variable"
}
})
This method by default will prevent your variable from being removed or replaced from the Vue prototype
If you want to take it a step further, let's say your variable is an object, and you don't want any changes applied to your object, you can use Object.freeze():
Object.defineProperty(Vue.prototype, '$yourVariable', {
get() {
return Object.freeze(yourGlobalImmutableObject)
}
})
A possibility is to declare the variable at the index.html because it is really global. It can be done adding a javascript method to return the value of the variable, and it will be READ ONLY. I did like that:
Supposing that I have 2 global variables (var1 and var2). Just add to the index.html header this code:
<script>
function getVar1() {
return 123;
}
function getVar2() {
return 456;
}
function getGlobal(varName) {
switch (varName) {
case 'var1': return 123;
case 'var2': return 456;
// ...
default: return 'unknown'
}
}
</script>
It's possible to do a method for each variable or use one single method with a parameter.
This solution works between different vuejs mixins, it a really global value.
in main.js (or any other js file)
export const variale ='someting'
in app.vue (or any other component)
import {key} from '../main.js' (file location)
define the key to a variable in data method and use it.
Simply define it in vite configuration
export default defineConfig({
root:'/var/www/html/a1.biz/admin',
define: {
appSubURL: JSON.stringify('/admin')
}, ..../// your other configurations
});
Now appSubURL will be accessible everywhere

How to Two-way Data Binding Between Parents and grandchildren in Vue.js

I faced a problem, I solve it by cookies but I want to solve the problem without cookies. I have a component which called app-header and It has another component which called outmodal.
Now, My first Vue instance require component app-header.
var vue = new Vue({
el : "html",
data : {
title : "Site Title",
description : "description of page",
keywords : "my keywords",
view : "home",
login : "login"
},
components:{
"app-header" :require("../../components/header"),
"app-footer" :require("../../components/footer"),
"home" :require("../../views/home")
},
});
code of app-header
var Vue = require("vue");
Vue.partial("login",require("../../partials/login.html"));
Vue.partial("logged",require("../../partials/logged.html"));
module.exports = {
template : require("./template.html"),
replace : true,
components : {
outmodal : require("../outmodal")
},
props : ['login']
}
code of outmodal
var Vue = require("vue");
Vue.partial("loginModal",require("../../partials/loginModal.html"));
module.exports = {
template : require("./template.html"),
replace : true,
props : ['name'],
data : function () {
return {
userLogin : { mail : "", password : "", remember : ""}
}
},
methods : {
formSubmit : function(e){
e.preventDefault();
this.$http.post("http://example.com/auth/login",{ "email": this.userLogin.mail , "password": this.userLogin.password },function(data,status,request){
$.cookie("site_token",data.token,{expires : 1})
}).error(function(data,status,request){
});
}
}, ready : function(){
console.log("it works")
}
}
In outmodal component I connect the API and I check the login, If login will be succesfull, I want to change value of login variable in my Vue instance. I use web pack to build all requires. So I don't know how can I data binding between these files.
How can I solve It? I
The Best Solution which I found
For 0.12
http://012.vuejs.org/guide/components.html#Inheriting_Parent_Scope
for 1.0
http://v1.vuejs.org/guide/components.html#Parent-Child-Communication
for 2.0
https://v2.vuejs.org/v2/guide/components.html#Composing-Components (use props to one-way bind data from parent to child)
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.
i found this one to be more accurate.
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
only in 2.3.0+ tho.
and honestly it's still not good enough. should simply be a easy option for 'two-way' data binding. so none of these options is good.
try using vuex instead. they have more options for such purpose.
https://vuex.vuejs.org/en/state.html
I would prefer event-driven updates as recommended in the documentation. However, I was limited by the existing ("third-party") component already using props and $emit. This component is my grandchild. The following is my solution (passing value through child using props, sync and computed value with $emit.
Comments are welcome.
Value can be modified in parent and grandchild without error:
Grandchild (simplified third-party component):
<template>
<div v-show="value">{{ value}}</div>
<button #click="closeBox">Close</button>
</template>
<script>
export default {
props: {
value: null
},
methods: {
closeBox() {
this.$emit('update:value', null);
}
}
}
</script>
Child:
<template>
<grandchild-component :value.sync="passedValue" />
</template>
<script>
export default {
props: {
value: null
},
computed: {
passedValue: {
get() {
return this.value;
},
set(newVal) {
this.$emit('update:value', newVal);
}
}
}
}
</script>
Parent:
<template>
<child-component :value.sync="value" />
</template>
<script>
export default {
data() {
return {
value: null,
}
},
// ... e.g. method setting/modifying the value
}
</script>

Resources