How to inject child router to the dialog view-model - aurelia

In our project we have bunch of custom elements like this:
<entity-link id="entity.id>
basically it just renders a link to edit entity screen
<template>
<a class="entity-link"
route-href="route: ENTITY_EDIT; params.bind: { id: entity.id }"
>${entity.name}
</a>
</template>
the problem is that this doesn't work at all within Aurelia Dialog context.
href attributed is not populated at all.
I tried to investigate the issue, I injected the router directly to dialog's view-model
import {Router} from 'aurelia-router';
#inject(DialogController, Router)
export default class RecordDetailsDialog {
constructor(dialogController:DialogController, router:Router) {
this.controller = dialogController;
this.router = router; /// WRONG INSTANCE!!!
}
}
and figured out the wrong instance of Router is being injected.
Main router (AppRouter) doesn't define ENTITY_EDIT route, it's added dynamically in child route configureRoute function.
I don't understand why the injected router is the main one instead of the one passed to the view which initiate dialog opening.
Any advice please

so after 2 hours of reading aurelia's source code I found out that DialogService instance is created within the root DI container which is associated with the root Router which is unaware about children routes.
I worked around our problem by registering DialogService instance manually within Child view-model container
import {Container} from 'aurelia-dependency-injection';
import {CompositionEngine} from 'aurelia-templating';
import {DialogService} from 'aurelia-dialog';
export class Main {
constructor(container:Container, compositionEngine:CompositionEngine){
container.registerInstance(DialogService, new DialogService(container, compositionEngine))`
}
...
}
but it feels hacky, still wondering if there is a more clean way to solve the problem.

Related

Is it component definition in vue? How do I find whether it is a component by looking at the code?

When I search for something I came across this post
Here, something, I believe it is component, is defined as below.
export default {
name: 'app',
methods: {
testFunction: function (event) {
console.log('test clicked')
}
},
components: {
Test
}
}
As per the documentation, I came across this
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
I really not sure, whether app is a component which contains Test. Is it the component definition in export? How do we understand that is a component from vue code?
Is it only by the below ways?
Vue.component
components in Vue instance
Not sure - the export default way?
I understand that I sound different because it is Javascript. Could someone help me with this?
There are conventionally, three different types of components in Vue JS.
Root Component
View Component
Normal Component
In a conventional structure the Root component is named 'app' and is passed to the Vue instance when initializing the App for the first time. This component can not be imported and reused inside other components as it will cause a recursive effect. For example this App.vue file is a Root component. It is used inside this main.js file and passed to the Vue instance using new Vue.
The View components are dynamically added or removed from the Root component based on the route. They are written and act as normal component and are only used for Vue router component property. For example the Home and Comments components inside this Router index file are known as View components. They are passed inside the route objects as components inside individual routes. When the app navigates to that particular route, the <router-view> template inside the App.vue file gets replace with the template of the corresponding View component.
Normal components can be used anywhere and are imported by other components. They can be imported inside the View components as well as the Root components. For example, in root component App.vue we see the component Navbar is used. In View component Comments.vue the Replies component is used.
All these components are identical in declaration and behavior but differ in usage.

Aurelia: change navigation in app.js from view

I have an Aurelia project with navigation in app.html and app.js. The project includes a home page that has a different style to it, including navigation that is different than the non-home page views.
I would like to turn off navigation for the home view so I tried setting a variable (showMenu) to toggle the visibility. In fact, I am able to use jQuery to do this, but I wonder if there is an Aurelia way of doing it. If I set this.showMenu to true it shows the menu container, and false hides it. Like this for example:
app.html
<div class="container" if.bind="showMenu">
app.js
constructor(router){
this.router = router;
this.showMenu = true;
...other things
}
What I would like to do is set showMenu to false from home.js. I tried this (among 20 or so other attempts), but it does not work.
home.js
activate() {
this.showMenu = false;
}
Is there a way through $parent or some other means to hide the menu in app.html using a view model?
EDIT
This works but it feels a little like a hack.
home.js
import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
#inject(Router)
export class Home {
constructor(router) {
this.router = router;
}
attached(){
$("#navbarMenu").hide();
this.router.refreshNavigation();
}
}
You should be able to use router to achieve that. Since this is required for one page only, you can have something like this assuming your route name is home (or you could use other properties of RouteConfig that you have set in configureRouter):
<div class="container" if.bind="router.currentInstruction.config.name !== 'home'">
I approach this problem by using separate shells. By default Aurelia will start your app with app.js (or ts). But you can change that default and also use the same command to redirect to a new shell after authentication.
In your main.ts (or .js) you will have a line to start your aurelia app:
aurelia.start().then(() => aurelia.setRoot());
This line is telling aurelia to start and to set the root view model for your app, when aurelia.setRoot() has no value given it defaults to app.ts (or .js).
So I create a landing for my app where I can display with the page and styles I wish completely separately from the main app, including a limited router and navigation.
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin('aurelia-testing');
}
aurelia.start().then(() => aurelia.setRoot('authPage'));
}
authPage.ts is my usual app.ts with a router configuration but it will only have the authPage configured in it and perhaps one or two other welcome pages.
The authPage takes care of authentication and obtaining appropriate tokens. I use a 3rd party for authentication services so all I have on this page is a link. Either way after successful authentication is confirmed you now just want to redirect to an alternative aurelia shell.
#autoinject
export class AuthPage {
private app : Aurelia;
private router : Router;
constructor(router : Router, app: Aurelia) {
this.app = app;
this.router = router;
}
authenticate {
//some kind of authentication procedure...
if(authenticationSuccess) {
this.router.navigate('/', { replace: true, trigger: false});
this.router.reset();
this.router.("authenticatedApp");
}
}
The lines this.router.navigate('/', { replace: true, trigger: false}); and this.router.reset(); are provided to deal with issues mentioned here and also on SO here. The shell switch line this.router.("authenticatedApp"); doesn't work for me without the other two.
My authenticatedApp configures a full router and navigation menu for the user in just the same way as you would normally do with app.ts but now separated into its own shell.
Of course there is nothing to prevent someone linking straight to authenticatedApp but at this point there is no data displayed without an api call which all require an access token to be presented.
This is a useful link on building an Aurelia app with multiple shells for authentication.
The end result is a separated landing pages and application pages which can have different styles and different navigation.On logout you can do the same thing in reverse to reload the auth page.

How to call mounted or created within a Vue plugin?

I am trying to create some plugins according to this article:
https://alligator.io/vuejs/creating-custom-plugins/
I have a plugin that needs to run something when the root Vue instance mounts or is created. So far I can only see a way to inject something into all components which is not what I would want.
I simply need to do something when the main Vue instance mounts. How can I do this with a plugin?
The install method from the plugin does not seem to do the trick because this seems to happen before the actual created method.
It's possible to have multiple root Vue components. A "root component" is just a component created with the new syntax and no parent component, so you can detect this as follows:
Vue.mixin({
created() {
if (!this.$parent) {
// This is either the root component or a component
// created with `new` and no parent
}
}
})
It's actually easy to include mixins for just a particular component!
In your component that you want to add the mixin to, just import it like you would anything else, and include an array in your component called mixins like so:
import myMixin from 'src/mixin/myMixin'
export default {
mixins: [myMixin]
}
Then your myMixin code would look like this (don't use Vue.mixin, which is global):
export default {
beforeMount () {
console.log('Your component is about to be mounted!')
}
}

Aurelia - View render before custom element is ready/bound

I have a very simple app with one view and one custom element.
app.html:
<template>
<require from="./content"></require>
hello
<content></content>
</template>
content.html:
<template>
${message}
</template>
content.js:
import {HttpClient} from 'aurelia-http-client';
import {inject} from 'aurelia-framework';
#inject(HttpClient)
export class Content {
constructor(http) {
this.http = http;
}
bind() {
return this.http.get('https://api.github.com/users/aurelia/repos')
.then(c => this.message = 'world');
}
}
Inside the bind life cycle event I have a REST service call (with a promise) which fetches me some data. The problem I have here is that if this asynchronous call take some time the view app.html will render before and then when the data is fetched it will be bound to the element. What I mean is that first the browser renders Hello and then after some time it renders world. I don't like this behavior on the specific site I'm working on. Instead I would prefer the browser to be blank and render all when everything is ready.
Much like if I'm working with server side rendering. Then the browser is waiting for the server to build up the complete response before it gets it and then renders it all.
The event activate() works in a view model but now I have a custom element. Is it possible to do something similar?
I need to fetch the data in the custom element not in the view model. Otherwise I know I could fetch it in the view model and later bind it to the element through a property. This is not possible for me.
Also I looked at this link but could not get it to work. Not sure if it is the same case as I have.
ANSWER
The link do in fact provide the correct answer. I can use the CompositionTransaction to have the view wait for the element. I believe I had some cached files and when changing the code it didn't work before I deleted the cache in my browser (chrome).
The answer is indeed to use CompositionTransaction in the case above.
This link shows an example of that.
The solution in my example is to change the view model content.js to something like this:
import {HttpClient} from 'aurelia-http-client';
import {inject, CompositionTransaction } from 'aurelia-framework';
#inject(HttpClient, CompositionTransaction)
export class Content {
constructor(http, compositionTransaction) {
this.http = http;
this.compositionTransactionNotifier = compositionTransaction.enlist();
}
bind() {
return this.http.get('https://api.github.com/users/aurelia/repos')
.then(c => {
this.message = 'world';
this.compositionTransactionNotifier.done();
});
}
}

How to get reference to the current router from ViewModel

In my viewmodel class, how do I get a reference to the current Router?
What I really want to do is get the current ModuleId.
In Durandal, there was system.getModuleId, but there is no system in Durandal, so I figure the router is going to have that information.
The way to get a reference to the current router:
import {inject} from 'aurelia-framework'
import {router} from 'aurelia-router'
#inject(router)
constructor(router)
{
this.router = router; //Do something with router.
}
Note: Do not inject "AppRouter" It's a different router. If you add a route to AppRouter, it will not work. It will work if you import Router.
One way (not sure, the optimal one) to access current moduleId is in activate hook of your class:
activate(params, routeConfig) {
console.log(routeConfig.moduleId);
}