We are developping an application with durandaljs and we seems to have issues with child routers or at least, something we don't understand or can't figure out. Let's start with an exemple.
Route => /part1/:id/part2/:id2/part3
The problem, the main.js activate method is called on every level of routing while navigating :
= Root (activate called)
== Part1 (activate called)
=== Part 2 (activate called)
==== Part 3 (activate called (normal since this is the initiator of everything else since that's where we have navigating to, no more child router declare here as it live in the child router declare in Part2))
We found that behavior kind of weird and wonder if this is the normal behavior or if we do something really wrong here with child routers. Why every sub level need to re-run their activation method? IHMO, there's only the current model where we are navigating to that should trigger his activate method and any other sub page shouldn't fire this event.
We are using the latest DurandalJS available which is 2.1.0 and we are building the application with Typescript, although I don't think it change something here.
This is the normal behavior. The activate function is propagated throughout the hierarchy whenever a child is activated. This does not match the deactivate behavior. Deactivating a child while the parent is still instantiated does not invoke the deactivate on the parent.
The best way to handle this is by switching on the arguments of the activator callback, since the activation arguments are passed to each activator callback. Thus, for part 1, you might have
activate(id1, id2) {
if (id1 === null && id2 === null) {
// logic
}
}
This misses the edge case where you navigate to /part1//part2, so if this is something concerned about, you can make a more robust check using regular expressions on the root router.
import router = require('plugins/router');
activate(id1, id2) {
var route = /^part1\/?$/;
if (route.test(router.activeInstruction)) {
// logic
}
}
Related
I want to know the concept of the below thing-
I have created one component and set up its respected event listeners. Now, I want to remove those listeners on this component's beforeDestroy hook before redirecting to another route that will create another component.
but what I noticed is, beforeDestory hook of the first component is calling even after the second component's created hook.
I want to destroy the first component completely and then create another component.
// To set up the event listeners
created() {
this.EventBus.$on('myCustomEvent', payload => {
// some code here
)}
}
// To destroy the event listeners
beforeDestroy() {
this.EventBus.$off('myCustomEvent');
}
Any suggestions?
In search of an answer to your question, I came to the conclusion that it is better to refuse to use EventBus altogether.
Here is some information on this and some thoughts from there:
I have that feeling that having an EventBus in Vue is an anti-pattern, especially if you’re using VueX but I can’t quite put my finger on it. At the point you want to be sharing data like that, wouldn’t it be better to use a store to handle all of those “events” / “mutations”?
Also, looking at the solution to this issue, you are doing everything right and there is no other way.
I have a custom Vue component that does not render to HTML.
When the component is first mounted, I am able to loop through this.$listeners and optimize the underlying non-HTML event implementation accordingly (e.g. not to emit mousemove type events unless something is listening).
To complete this process I'd like to know when listeners are programatically added later through $on().
Is there any non-polling way to be notified of this? My current workaround is to listen for and emit everything but that is a poor solution.
Note that in some instances I want to be able to use the events in question, and others not. For example:
<custom-component #eventThatWouldFireOften="doSomething" #anotherEvent="doMoreStuff"/>
and another usage might be simply.
<custom-component ref="custom"/>
...
mounted() {
this.$refs.custom.$on('anotherEvent', ...)
}
So in the first case the result would be:
CustomComponent tells the underlying API it wants to listen for eventThatWouldFireOften and anotherEvent
CustomComponent receives eventThatWouldFireOften and anotherEvent events from the underlying API and re-emits them as Vue events that can be listened to using v-on or # syntax.
..and the second case the result would be:
CustomComponent tells underlying API it doesn't want to listen for anything just yet
When the parent of CustomComponent is mounted it programatically listens for anotherEvent. That needs to be communicated down to the base API (what I'm trying to solve).
In my Durandal application, I have configured several parent routers and for some of it , there are child routers configured. The problem is, when the user navigates between different parent pages or the child pages, then binding lifecycle of the previous route is also getting triggered along with the binding lifecycle of the existing route.
The child routers are configured inside the parent view models and the parent routers are configured in shell.ts as mentioned in Durandal docs.
Provided, in my shell.ts, in the router data bind property, i have given the attribute cacheViews as false and alwaysTriggerAttach as true.
<div data-bind="router:{cacheViews: false, alwaysTriggerAttach: true}"></div>
The expected result is, the binding lifecycle of the previous route should not be loaded when the current route is loading. For example if we are navigating to #P1/C1 from #P2, then the binding lifecycle of #P2 should not get loaded.
Without getting enough details, it's hard to answer this question correctly.
But looks like domElements which are removed from DOM still has eventHandlers active on them. Have you looked into http://durandaljs.com/documentation/api#class/Router/method/reset and tried using it?
Default behaviour in knockout is even if DOM elements are removed from dom, it still leaves eventHandlers attached, in active state and doesn't remove them by default.
In your router bindingHandler, inside init method, try below and see if it fixes
ko.utils.domeNodeDisposal.addDisposeCallback(element,()=>{
//do whatever you want here. like cleanup //reset(), etc
})
https://knockoutjs.com/documentation/custom-bindings-disposal.html
I'm trying to show an activity indicator, when I go from one page to another. The target page contains many components within it, and it takes time to load. that's why I need some way to listen when all the child components are loaded, and at that moment tell my variable isBussy to be false
<template>
<StackLayout>
<ActivityIndicator :busy="isBussy" v-if="isBussy" />
<StackLayout v-else>
<Component1 />
<Component2 />
<Component3 />
<Component4 />
</StackLayout>
<StackLayout>
</template>
<script>
import Component1 from '~/components/Component1'
import Component2 from '~/components/Component2'
import Component3 from '~/components/Component3'
import Component4 from '~/components/Component4'
export default {
data() {
return {
isBussy: true
}
},
mounted() {
this.$nextTick(function() {
// Code that will run only after the
// entire view has been re-rendered
this.isBussy = false
})
}
}
</script>
this code does not work, since once the navigation is indicated from the previous page with:
#tap="$goto('otherPage', { props: { foo: bar } })"
it remains stuck on the initial page, and all the components begin to load in the background of the destination page, but without displaying the parent page, changing to this, only when the whole process ends, and never show/hide the activity indicator as expected.
By the way this expected behavior works perfectly when i do request and process them with Promises, then I turn on or off a variable in the state and it works. but I can not replicate that behavior in the navigation between pages and listen to load all the components
EDIT
Finally I achieved the desired behavior with a little trick I found on the internet
mounted() {
setTimeout(() => {
this.isBussy = false
}, 500)
},
this causes that the rendering of all the children components is delayed only a little, so that the activity indicator is shown, but not too much to produce that none of the components contained in the else block is detected and begin to rendering
There are two main ideas to understand here I think. I'll describe both.
1. General technique to Fetch Data without blocking render
It sounds like you understand this concept at the parent component level but then are asking how to do something very similar for the child components that this page contains.
The way I handle this, is in my component, I have my data default to an isLoading state. Then, in beforeMount() or mounted(), I perform my asynchronous actions and make necessary changes to my page's data.
The problem becomes entirely recursive when we look at child components. You want to make sure your child components are rendering and that any long running data fetching that needs to occur within their implementation will simply cause them to re-render once that fetching is complete.
Here is a working example: https://codesandbox.io/embed/r4o56o3olp
This example uses Nuxt. Aside from the addition fetch() and asyncData() methods, the rest of the Vue lifecycle hooks are the same here.
I use new Promise and setTimeout to demonstrate an operation that would use promises and be asynchronous. (e.g. axios.get(..))
The About page loads, and the beforeMount() lifecycle hook performs the asynchronous fetching in a way that doesn't block the page from rendering.
I use the beforeMount() hook because, according to here ( https://alligator.io/vuejs/component-lifecycle/ ), it is the first lifecycle hook that we have access to once the page's data is reactive. (So modifying this.myDataProp would trigger a re-render if {{ myDataProp }} was used in the template).
I also included a child component where I purposely made its data take twice as long to load. Since I again, am letting the component render immediately, and then I handle the fetching/updating of data in an appropriate lifecycle hook, I can manage when the end-user perceives a page to be loaded.
In my working example, the LongLoadingComponent did the same exact technique as the About page.
Once you see how to use beforeMount() or mounted() to fetch data and then update state, I think the trick is to take a moment and really think about the default state of your component. When it first renders, what should the user see before any of it's data fetching/long-running operations are completed?
Once you determine what your default (not yet loaded) component should look like, try getting that to render on your screen, and secondarily add in the logic that fetches and updates state data.
2. Listening for when a Child Component is finished rendering from a parent component
This makes use of the above technique, but includes the usage of the updated() hook and emitting a custom event ( https://v2.vuejs.org/v2/guide/components-custom-events.html
)
If you really want to listen for when your child components are finished rendering, you can $emit a custom event in your updated() hook. Perhaps something like this (in one of your child components)
if (this.dataLoaded) { this.$emit('loadedAndRendered') }
So when the child's async operations are done, it can flip it's dataLoaded property to true. If dataLoaded is used in the child's <template> somewhere, then the component should re-render (for it's "finished" state). When the child re-renders, the updated() hook should trigger. (again, see: https://alligator.io/vuejs/component-lifecycle/ ) I included the if (this.dataLoaded) part just to handle case where updated() hook might be called during intermediate data updates. (We only want to emit loadedAndRendered event if child is finished loading data/updating.)
3. Other caveats about universal nuxt applications
It wasn't until after I wrote this answer that I realized you aren't using Nuxt. However I'm adding this in case other Nuxt users happen to come across this.
I'm adding this section just because it took some focused hands-on time for me to wrap my head around. A Nuxt Universal Application does both server-side and client-side rendering. Understanding when something renders on the client vs when it was rendered on the server was a little difficult for me at first. In the working example I linked above, when you visit the about page you can also see if that component was fetched from the server or if it was just rendered by the client.
I'd recommend playing with a Page's fetch() and asyncData() methods and see how it impacts when certain things render on your screen. ( https://nuxtjs.org/api/pages-fetch/ ) ( https://nuxtjs.org/api/ ). Seeing what these methods are useful for helps me also identify what they are not useful for.
If you're using a Vuex store, I'd recommend seeing what happens when you refresh a page or use instead of a to navigate between pages. (Seeing something like the SSR schema diagram can be helpful here: https://nuxtjs.org/guide#schema )
..I have yet to fully appreciate the details of the bundling and delivery behavior that Webpack provides for a Universal Nuxt app (See right side of diagram here: https://medium.freecodecamp.org/universal-application-code-structure-in-nuxt-js-4cd014cc0baa )
Can we have a state base routing in Aurelia JS like angular ui router
Currently I have routes like this
root/<userid>/<feature>.
Now I am just trying to implement the same in following way
root/<userid> and feature should be pass as a parameter.The problem is that
Once the component is loaded in memory it is not getting updated on params change.
TIA
You need to configure the lifecycle to be invoked any time the URL parameters change.
See "Reusing an Existing VM" at the link below.
http://aurelia.io/hub#/doc/article/aurelia/framework/latest/cheat-sheet/7
"Since the VM's navigation life-cycle is called only once you may have problems recognizing that the user switched the route from Product A to Product B (see below). To work around this issue implement the method determineActivationStrategy in your VM and return hints for the router about what you'd like to happen."
In your view model write the following method:
determineActivationStrategy {
return activationStrategy.invokeLifecycle;
}