Run function AFTER route fully rendered in Nuxt.js - javascript

Background:
I'm building an SSR website using Nuxt. I want to run a script to fix some typography issues (orphaned text in headers). I can't do this UNTIL AFTER the DOM is rendered. How can I implement this function once so it runs after each page's DOM is rendered? It can be either in the Router or in a Nuxt Layout, or elsewhere.
What I've tried:
In my layout.vue, Mounted() only runs on the first load (as expected) and adding $nextTick doesn't seem to affect that. This is even true for generated static pages served from a real webserver.
In my layout.vue, using Vue's Updated() never seems to fire. I assume this means Nuxt is getting in the way.
Using app.router.afterEach() the function runs on each route change (including first load), but way before the DOM is rendered making it worthless.
If I add Vue.nextTick() into the .afterEach() the function runs on the current page JUST BEFORE the route changes (you can see it flash) but DOES NOT run before that.
What works but seems dumb:
Putting the function in the Mounted() block on each page.
mounted: function(){
this.$nextTick(function () {
const tm = new TypeMate(undefined, { selector: 'h2, h3, p, li' });
tm.apply();
})
},
But this seems like a bad idea especially as we add pages. What am I missing? Is there a smart way to do this? Nuxt's documentation is next to useless for some of this stuff.

You can create mixin with mounted function. Lifecycle hooks from mixin will be merged with your lifecycle events and each will be run.

Related

The recommended way to load route data before entering the route?

Before rendering a page for a given route, I'd like to synchronously fetch the necessary data first. Ideally, I'd like to have the data fetching within the page component, but I'm not opposed to doing it in the router files. I've read and tried various ways of doing it, but part of the challenge comes from the fact that there are also multiple ways of building components and the usage of certain features vary.
In my case, I'm building single file components using the Composition API and <script setup> syntax. The Vue Router documentation link talks about "fetching before navigation" in which I could reach for beforeRouteEnter or beforeRouteUpdate, but this is shown using the Options API. They do have the page for the Composition API mentioning I could use onBeforeRouteUpdate, but that uses the setup() function. I figured I'd try it out anyway with <script setup>:
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteUpdate(() => {
console.log('onBeforeRouteUpdate')
})
</script>
However, this does not execute. The closest method I've tried that works is fetching the data in the router, using the beforeEnter guard, and setting the data onto the meta property, which can then get accessed on the route instance in the component:
beforeEnter: (to, from, next) => {
fetch('https://pokeapi.co/api/v2/pokemon/ditto')
.then(res => res.json())
.then(res => {
to.meta.pokemon = res;
next();
});
}
But with this, which is noted in the documentation, beforeEnter only triggers when entering the route. Params changes will not retrigger this, meaning that I'd have to set up a watcher on the route in the component anyway. I might as well just have had all this logic in the component itself.
I just can't seem to find a good way to do this, but I might have overlooked something. If anyone has some pointers or advice, I'd appreciate it. Thanks in advance.
There is a solution - using top level await - https://vuejs.org/api/sfc-script-setup.html#top-level-await
Just wrap your RouterView component in a Suspense component like shown here - https://vuejs.org/guide/built-ins/suspense.html#combining-with-other-components (don't use the components you don't need)
The only caveat is that the 'loading screen' will be visible on the initial request.
I made a little demo for you so you can try it out - https://github.com/ileue/vue-top-level-await-demo
First off, beforeRouteUpdate is only triggered when updating the actual route but not going to another component/page as officially told here.
An example on what could trigger that lifecycle hook would be
<button #click="$router.push({ hash: `#${Math.floor(Math.random() * 10)}` })">
random hash
</button>
onBeforeRouteLeave perfectly works tho, as you can expect, when moving from page to page.
As for the initial question, you could implement some kind of router middleware like Nuxt does it. That way, you could await an HTTP call and only then allow for an actual navigation. Hence creating a block navigation effect pretty much.
I'm not sure on how to write that with Composition API, but I know that it perfectly works with Options API (quite some blog posts available). setup by itself behaving in it's own life-cycly way, I guess quite some things are rather tricky.
TLDR: a good ol' router middleware + wrapper around your pages (like a layout) is the perfect combo in your case IMO. There, you could set a single watcher for quite a lot of pages at the same time.
But everything depends on how you want to organize yourself and structure your code of course.
Skeleton screens bring a sense of being faster than something blocking but overall, you could also use prefetch (coming with Nuxt too by default) to get some hints and potentially load some assets even before they are needed. (+ other tricks in the same domain to speed up your network requests)

Load undefined component via ajax with vue3 after app instancing?

I would like to know which is the way to load an new component inside vue3 app instance. As far as I know its only possible to use components predefined during createApp() or via some internal trickery even after initialization but never on runtime after use.
The problem is than I cant find a way to fetch the component at runtime, via an ajax call so instead of throwing "Failed to resolve component: mycomponent" it would check the server and import the component.
Yes, I know about defineAsyncComponent and vue3-sfc-loader but none of them seem to handle undefined components. They all seem to require the definition of all the components that we will be using. Or no?
var vapp = Vue.createApp({
template: '<mycomponent></mycomponent>',
components: {} /** not defined at runtime **/,
_importUndefinedComponent: function(tagname) {
$.get('/components/' + tagname + '.js');
}
});
For now I think that it's possible to edit the internal workings of Vue, as it seems that vue already uses promises to load async components....
A usecase for dynamic components. I have an crud system with hundreds of components. I want to make quick prototypes. So this way all I have to do is just upload the component to the right folder an use it. No build step and no fancy stuff. All components are using options api. So basically it's just an Object which I will eval.

ESLint error: Dependency cycle detected import/no-cycle

The requirement of the project is to separate DOM from logic in different files.
Thus, I have these files:
default.js
import { submitForm } from './logic'
function displayTaskForm(key) {
// function create form HTML content with btn submit.
submitForm(addTaskbtn, key);
return form;
}
export { displayTable };
logic.js
import { displayTable } from './default';
const submitForm = (btn, key) => {
// Adds event listener to add input to the table && local storage
displayTable(key) // shows the table content including recent input
return btn;
}
export { submitForm };
And I have these errors:
default.js
1:1 error Dependency cycle detected import/no-cycle
logic.js
1:1 error Dependency cycle detected import/no-cycle
Could you advise how I could solve this issue? the problem is I have to split my DOM from the logic part. I don't see a way to link creating DOM based on if statement results without linking both files with exporting & importing functions to each other.
Here is the original repository link
I think the issue might be rooted in where you've decided to draw the line between UI-related code, and logic. Here's how I draw it:
UI: anything that touches the DOM - this includes creating elements, attaching event listeners, etc. The UI can call out to logic for help.
logic: A bunch of helper functions (maybe classes if you're into that), which are mostly pure. These are functions in which the UI can delegate difficult tasks to. A DOM element should never be seen by this logic module. The logic should never depend upon a specific UI (so it shouldn't ever import the UI).
Another way to think of it - if I were to toss your current UI file and build a CLI version of your program that ran in node, would I have to make any changes to your logic file? If so, then your logic is touching UI-specific things it shouldn't be touching.
It's a little hard to tell, looking at your examples what's going on in your specific scenario, and your GitHub link currently doesn't have any code in it. But, hopefully, these guiding principles should help out. It could be that this circular dependency is really just showing that something that currently sets in the logic file needs to be partially or entirely moved to the UI file.
If you find that most to all of your code qualifies as UI-related code, then it could be that you need to work on finding and extracting out the pure parts of a chunk of logic and making that piece of logic able to stand by itself and be reusable in other contexts. Or, it could be that your particular webpage mostly deals with logic that's highly coupled with the UI.

simulate fbasyncInit behaviour on a webpack bundled library

Im using webpack to build a library targeted for browsers. And i Would really like to replicate fbasyncinit behavior without manually modyfing the bundled library.
Does webpack provide any way to call a function after loading the library itself ?
Or is there another alternative bundler that allows this?
For those unnaware, window.fbasyncinit is a function facebook sdk calls when finish loading, so you write the function to initialize facebook sdk stuff.
Depending on your setup you might want to make your library external and lazy load it into the target application. In a nutshell this means that your library exists alongside the application but won't actually be loaded on initialization. It could then be loaded by an event or after some setTimeout.
Webpack watches for special comments to tell it what code needs to be separated into its own bundle:
button.onclick = event => {
const loadLodash = await import(/* webpackChunkName: "lodash" */ "lodash");
console.log('lodash loaded');
};
This kind of comment,/* webpackChunkName: "lodash" */, is one such type. Note that until whatever event or timeout occurs, your library will not be loaded.
To be honest I haven't done this for libraries in particular so I'm not sure of the difficulties this might impose here.
Update
Actually, there might be a simpler answer to this. I forgot that lazy loading is actually just dynamically creating a script tag with a src to the library you want to load (using a bundled lodash library for example).
var script = document.createElement('script');
script.src = 'http://localhost:8080/vendors.[hash].js';
document.head.appendChild(script);
If you need to control when your library is loaded you could just trigger it this way after some other code (something that just exists to load the library) detects a page load.
Update 2
After rereading this, it may also be just as simple as triggering your fbasyncinit simulated behavior after some event when you know the library is loaded into the app (assuming your library doesn't depend on any network calls to initialize). All of the other ideas apply as well, but may be more complicated than you need. Before trying the external/lazy-load solutions I'd suggest looking at something simple like this within your library:
window.addEventListener('load', event => {
// your custom fbasyncinit behavior here
});
This is of course after everything in the page has been loaded, which may be too late depending on your needs.

Google Cast is="google-cast-button" Not Loading When Inside Angular Component

I am trying to add Google Cast to my music app. It works properly when I include the button and the script in the index.html file. However, when I move the button to a component, it no longer registers the button. I have tried adding this code to the components constructor as a way of delaying the load time of the cast library but it still doesn't register:
let script = window['document'].createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1');
window['document'].body.appendChild(script);
I copied this idea from the source of this npm:
https://www.npmjs.com/package/ng-cast
Any idea on how I can get the is="google-cast-button" to register late in the load process?
The most robust solution in my opinion is to add schemas: [ CUSTOM_ELEMENTS_SCHEMA ] to either your app.module.ts or feature.module.ts. By doing so, you can place the chrome cast button in your app with <google-cast-launcher></google-cast-launcher>, which is far more stable than messing with the DOM manually within your component.
I found this with react.js - if the button is created via innerHTML, then the 'is=' scripts of the web component won't run. The only way to get the scripts to run on a dynamically created button is to use the document.createElement syntax. This may mean digging in to angular to figure out how to create one that way. In react, it means waiting for componentDidMount and inserting the element into the DOM that way.
So not a BIG help, but maybe a hint of what to try next (granted it has been a few months).

Categories