Server side singleton injection in Nuxt - vue.js

I need a shared object (e.g.cache/logger/service) instance (singleton) on serverside accessible to SS middleware/plugins/nuxtserverinit.
I have tried a local module which tries to inject $cache in serverside context during render:done hook (see below), but no matter what I tried it still was not available during SS request processing.
// modules/myCache.js
export default function(_moduleOptions,config) {
this.nuxt.hook("render:before", context => {
const cache=new myExoticCache()
// I tried all the below combinations
context.nuxt.$cache1=cache
context.serverContext.$cache2=cache
context.options.$cache3=cache
context.globals.$cache4=cache
});
this.nuxt.hook("render:done", context => {
// tried the above here too
});
}
// plugins/myplug.js
export default ({serverContext,nuxt}, inject) => {
//all of the below are undefined
//nuxt.$cache
//serverContext.$cache
}
Seems like I am missing something. Would be great to find out what.
How can I pass value from route:done hook to any server-side middleware/plugin/nuxtserverinit.

You can extend ssrContext from 'vue-renderer:ssr:prepareContext' hook.
// modules/myCache.js
export default function(_moduleOptions) {
const $cache = 'CACHE';
this.nuxt.hook('vue-renderer:ssr:prepareContext', ssrContext => {
ssrContext.$cache = $cache;
})
}
// plugins/myplug.js
export default function ({ ssrContext }) {
if (process.server) {
console.log(ssrContext.$cache)
}
}

Related

Use dependency from boot file in other boot file in Quasar (vuejs)

I am trying to use an instance of an object in a boot file where the instance is created in another boot file. The documentation [0] talks about using an object instance from a boot file and it works fine when using the instance in a component. I would like to access the instance in another boot file.
First boot file that creates the instance looks like this:
import { AuthService } from '../authorization/AuthService';
let oidc = null
export default ({ router, store, Vue }) => {
const OIDC = new AuthService();
router.beforeEach((to, from, next) => {
const allowAnonymous = to.matched.some(record => record.meta.allowAnonymous)
if (allowAnonymous) {
next()
} else {
var isAuthenticated = OIDC.isAuthenticated()
if (isAuthenticated) {
next()
} else {
OIDC.signIn()
}
}
})
Vue.prototype.$oidc = OIDC
oidc = OIDC
}
export { oidc }
And I am trying to use the oidc instance in another boot file like this:
import oidc from "boot/oidc-service"
import axios from 'axios'
let axiosInstance = null;
export default ({ app, router, store, Vue }) => {
const AxiosInstance = axios.create({
baseURL: window.env.BASE_URL
})
AxiosInstance.interceptors.request.use(function (config) {
return oidc.getAccessToken().then(token => {
config.headers.Authorization = `Bearer ${token}`
return config
})
}, (error) => {
return Promise.reject(error)
})
Vue.prototype.$axios = AxiosInstance
axiosInstance = AxiosInstance
}
export { axiosInstance }
I import them in the following order:
boot: [
'oidc-service',
'axios',
...
If I export the class instead of the instance, I can instantiate it and code works as expected. I would like for the oidc object to be a singleton however.
How can I use the instance of oidc in my axios setup?
[0] https://quasar.dev/quasar-cli/boot-files#Accessing-data-from-boot-files
Unless I'm missing something... if oidc is not null, return it, otherwise continue with the initialization:
import { AuthService } from '../authorization/AuthService';
let oidc = null
export default ({ router, store, Vue }) => {
if(oidc !== null) return oidc;
// else continue...

Access Nuxt Router in plugin

I've created a Nuxt plugin that is loaded in the config file with some global functions. In one function, I'd like to access the router, and push to a new route. I am getting an error that router is undefined. Can someone help me understand how I access the router here, if its not attached to the context.
export default (context, inject) => {
const someFunction = () => {
context.router.push({ name: 'route-name' } })
}
}
Try to destruct the context then use app to access the router :
export default ({app}, inject) => {
const someFunction = () => {
app.router.push({ name: 'route-name' } })
}
}
I figured this out. When using context, you can access the router, like this:
context.app.router

Check if vuex-persist has restored data in Nuxt project

I have added Vuex-Persist and Vuex-ORM to my Nuxt project. When the application starts for the first time I want to add some boilerplate data.
In my default.vue layout I have added a created function to add this dummy data.
<script>
import Project from '../models/Project';
export default {
created () {
// I'm Using Vuex-Orm to check if there are any projects stored
if (Project.query().count() === 0) {
Project.insert({ data: [{ title: 'My first project' }] })
}
}
}
</script>
When the application is reloaded and opens for the second time, I would expect that Project.query().count()
returns 1. But it will always return 0 because vuex-persist isn't done restoring the local data yet.
According to the docs this would be the solution
import { store } from '#/store' // ...or wherever your `vuex` store is defined
const waitForStorageToBeReady = async (to, from, next) => {
await store.restored
next()
}
store.defined is undefined and same goes for this.$store.
That comment highlights my exact question "Where is my vuex store defined?"
i think you have to put the whole thing in a route guard.
create a route-guard.js plugin like this. but I haven't tested the whole thing, hope it helps you further.
export default function ({app}) {
const waitForStorageToBeReady = async (to, from, next) => {
await store.restored
next()
}
app.router.beforeEach(waitForStorageToBeReady);
}
Another option is to put a getter in computed and watch it:
export default {
computed: {
persistState() {
return this.store.getter['get_persis_state'];
}
},
watch: {
persistState(newVal) {
// check new val
}
}
}
I followed the instructions from vuex-persist for Nuxt and made a plugin file, like this:
// ~/plugins/vuex-persist.js
import VuexPersistence from 'vuex-persist'
export default ({ store }) => {
window.onNuxtReady(() => {
new VuexPersistence({
/* your options */
}).plugin(store);
});
}
window.onNuxtReady caused the plugin to be loaded after all other code had run. So I didn't matter if I made a router-guard.js plugin or tried it in the layout/default.vue file.
I ended up with the quick fix:
// ~/plugins/vuex-persist.js
import VuexPersistence from 'vuex-persist'
export default ({ store }) => {
window.onNuxtReady(() => {
new VuexPersistence().plugin(store);
if (Project.query().count() === 0) {
Project.insert({ data: [{ title: 'My first project' }] })
}
});
}

Unable to access vuex-orm $db variable from store in nuxt

Using nuxtjs with vuexorm & vuexorm-axios plugin.
/pages/index.vue
computed: {
users() {
// this works as expected
return User.all()
}
}
plugins/vuex-orm-axios.js
import { Model } from '#vuex-orm/core'
export default ({ $axios }) => {
Model.setAxios($axios)
}
store/index.js
import VuexORM from '#vuex-orm/core'
import VuexORMAxios from '#vuex-orm/plugin-axios'
import User from '#/models/user'
VuexORM.use(VuexORMAxios)
// Create a new database instance.
const database = new VuexORM.Database()
// Register Models to the database.
database.register(User)
export const plugins = [
VuexORM.install(database)
]
Above all works. But in vuexorm docs it says to always fetch model from injected database instance for nuxt/ssr apps.
But if I try to access the $db variable from store it wont work as there is no $db variable inside store.
/pages/index.vue
computed: {
users() {
// this wont work as $db is undefined
User () {
return this.$store.$db().model('users')
},
users () {
return this.User.all()
}
}
What am I doing wrong here?
In your store/index.js you have to export state to activate Vuex store. Add this to the file:
export const state = () => ({})

Aurelia common class/model

I'm trying to implement a globally-accessible singular class in an Aurelia project. The purposes are to (a) store singulares/states like current user ID/name/permissions, (b) load and store common data like enum lists and key-value pairs for drop-down lists across the whole app, (c) store commonly-used functions like wrappers for Http-Fetch client, (d) configure and then update i18n locale, (e) global keyboard listener for hotkeys throughout the app. Here's what I have so far:
/src/resources/components/core.js:
import 'fetch';
import { HttpClient, json } from 'aurelia-fetch-client';
import { inject } from 'aurelia-framework';
import { EventAggregator } from 'aurelia-event-aggregator';
import { BindingSignaler } from 'aurelia-templating-resources';
import { I18N } from 'aurelia-i18n';
import * as store from 'store';
#inject(EventAggregator, BindingSignaler, I18N, HttpClient)
export class Core {
constructor(eventAggregator, bindingSignaler, i18n, httpClient) {
// store local handles
this.eventAggregator = eventAggregator;
this.bindingSignaler = bindingSignaler;
this.i18n = i18n;
// initialize singulars
this.UserID = 1;
this.lang = 'es';
this.yr = 78;
this.qtr = 1;
// set up httpClient
httpClient.configure(config => {
config
.withBaseUrl('http://localhost:8080/api/v1');
});
this.httpClient = httpClient;
// listen for Ctrl+S or Ctrl+Enter and publish event
window.addEventListener("keydown", (event) => {
if (event.ctrlKey || event.metaKey) { // Ctrl + ___
if ((event.keyCode == 83) || (event.keyCode == 115) || (event.keyCode == 10) || (event.keyCode == 13)) { // Ctrl+Enter or Ctrl+S
// Save button... publish new event
event.preventDefault();
this.eventAggregator.publish('ewKeyboardShortcutSave', true);
}
if ((event.keyCode == 77) || (event.keyCode == 109)) { // Ctrl+M
// New button... publish new event
event.preventDefault();
this.eventAggregator.publish('ewKeyboardShortcutNew', true);
}
}
});
// load enumData
$.getJSON("../../locales/" + this.lang + "/enum.json", (json) => { this.enum = json; });
this.getTableKeys();
this.getEnumCats();
}
getData(url) {
// Http Fetch Client to retreive data (GET)
return this.httpClient.fetch(url)
.then(response => response.json());
}
postData(url, data, use_method = 'post') {
// Http Fetch Client to send data (POST/PUT/DELETE)
return this.httpClient.fetch(url, {
method: use_method,
body: json(data)
}).then(response => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
});
}
getTableKeys() {
// retrieve list of table keys from database API
this.getData('/keys').then(response => {
this.keys = response;
});
}
getEnumCats() {
// retrieve list of enum cats from database API
this.getData('/enums').then(response => {
this.cats = response;
});
}
setLang(lang) {
if (lang) {
this.lang = lang;
}
// set i18n locale
this.i18n.setLocale(this.lang);
// load enumData
$.getJSON("../../locales/" + this.lang + "/enum.json", (json) => {
this.enumData = json;
});
// publish new event
this.eventAggregator.publish('ewLang', lang);
this.bindingSignaler.signal('ewLang');
}
}
Here's the /src/resources/index.js for the resources feature:
export function configure(config) {
// value converters
config.globalResources([
'./value-converters/currency-format-value-converter',
'./value-converters/number-format-value-converter',
'./value-converters/date-format-value-converter',
'./value-converters/checkbox-value-converter',
'./value-converters/keys-value-converter',
'./value-converters/enum-value-converter',
'./value-converters/table-key-value-converter'
]);
// custom elements
config.globalResources([
'./elements/enum-list',
'./elements/modal-form'
]);
// common/core components
config.globalResources([
'./components/core'
]);
}
which is in turn activated in my main.js like this:
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.feature('resources')
// .plugin('aurelia-dialog') // not working
.plugin('aurelia-validation')
.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(XHR);
instance.setup({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
lng : 'en',
ns: ['translation'],
defaultNS: 'translation',
attributes : ['t'],
fallbackLng : 'en',
debug : false
});
});
aurelia.start().then(a => a.setRoot());
}
Questions:
It's not working. I get two errors: vendor-bundle.js:3777 Uncaught TypeError: h.load is not a function and Unhandled rejection Error: Load timeout for modules: template-registry-entry!resources/components/core.html,text!resources/components/core.html. Any idea why it's trying to find a core.html when I only need the core.js component?
Is it even possible to globally inject this type of class in a way that my viewmodels don't need to inject it but can still access the properties, or do I still need to inject this file everywhere?
Is the filename core.js and the class name Core acceptable naming conventions? Is the location inside /src/resources/components a good choice? I had to create the components subfolder.
Any other suggestions for better best practices?
Question 1
When you do this:
config.globalResources([
'./components/core'
]);
Aurelia will try to load a pair of view and view-model, respectively core.js and core.html, unless if the component is declared as a "view-model only component". Like this:
import { noView } from 'aurelia-framework';
#noView
#inject(EventAggregator, BindingSignaler, I18N, HttpClient)
export class Core {
}
In the above case Aurelia won't try to load "core.html" because the component is declared with noView.
Question 2
As far as I know, you have to inject or <require> it everywhere, but the latter doesn't apply in your case, so you have to inject. You could some trickery to avoid the injecting but I would not recommend.
Question 3
The file name core.js and the class name Core are not only acceptable but the correct aurelia-way of doing this. However, I don't think that "/resources/components" is a good a location because it's not a component, not even a "resource". I would move this to another folder.
In addition, remove these lines:
config.globalResources([
'./components/core'
]);
Resources were made to be used inside views, which is not you are case.
Question 4
The file core.js seems to be a very import piece of code of your application. I would leave it inside the root folder, next to main.js. (THIS IS MY OPINION)
Also, if you need to set some specific properties in your Core object, you can instantiate it inside the main.js. Something like this:
export function configure(aurelia) {
//...
Core core = new Core(); //<--- put necessary parameters
//some default configuration
aurelia.container.registerInstance(Core, core);
aurelia.start().then(a => a.setRoot());
}
Now, you can inject the core object using the #inject decorator and all classe will have the same instance of Core. More information at http://aurelia.io/hub.html#/doc/article/aurelia/dependency-injection/latest/dependency-injection-basics/1
Hope this helps!