How can Aurelia child routing work with a parameter? - aurelia

I feel as though similar questions have already been asked, but I have been unable to find my answer.
I'm trying to segregate my application by its features. Ideally each feature would be able to setup its own routing as well and Aurelia's child-router functionality seemed to be the perfect fit, but I'm having trouble getting it to work.
The structure of the application is as such:
app.ts
app.html
/lectures
list.ts
list.html
details.ts
details.html
index.ts
index.html
I can include any of the other files if needed to answer the question, but have tried to keep the question as compact as possible. The app.html and lectures/index.html files both only contain <template><router-outlet></router-outlet></template>.
I have app.ts:
import { Router, RouterConfiguration } from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration, router: Router) {
config.options.pushState = true;
config.map([
{
moduleId: './public-site/lectures',
name: 'lectures',
nav: true,
route: ['', 'lectures/:id?'],
title: 'Lectures'
}
]);
}
}
lecture/index.ts
import { Router, RouterConfiguration } from 'aurelia-router';
export class Index {
configureRouter(config: RouterConfiguration, router: Router) {
config.options.pushState = true;
config.map([
{ route: '', moduleId: './list' },
{ route: ':id', moduleId: './details' }
]);
}
}
and then I have lectures/details.ts
import { NavigationInstruction, RouteConfig, RoutableComponentActivate } from 'aurelia-router';
export class LectureDetails implements RoutableComponentActivate {
activate(params: any, routeConfig: RouteConfig, navigationInstruction: NavigationInstruction): Promise<any> {
debugger;
}
}
and lecture\list.html
<template>
<div repeat.for="lecture of lectures" class="grid-body-cell" click.delegate="navigateToLecture(lecture)">
${lecture.title}
</div>
</template>
lecture\list.ts
import { autoinject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
#autoinject()
export class LecturesList {
constructor(private router: Router) {}
navigateToLecture(lecture: {id:number}) {
this.router.navigate(`#/lectures/${lecture.id}`);
}
}
When the app loads, it correctly navigates and displays the list page, but when I click on any of the lectures in the grid, the url updates to /lectures/1, but my debugger statement never gets hit. I can't figure out what I'm doing wrong.
What seems to be happening is that, while the url gets updated, the router is still directing the application to the list component. Is there a way to get the router to honor and pass on the parameter to the child router?
How do I need to update my setup to get the child router to work with the parameter?

Firstly, the router element for displaying routed views is called <router-view> so your views for child routers should be: <template><router-view></router-view></template> - I believe <router-outlet> is what you use in Angular 2+ applications for routing. I am making the assumption here you are already doing that if you're seeing things being rendered.
Secondly, you have config.options.pushState = true defined on your root router configuration and then inside of your navigateToLecture method you are passing in a hash (which is what you would do if you're not using pushState). So Aurelia is removing the hash from the URL (as intended) because you're using pushState and pushState URL's don't need to use the # hack.
Thirdly, I would name your routes (and you'll discover why in a moment). Naming your routes allows you to reference them by name and use either navigateToRoute('routename', {paramsobject}) or route-href (which we discuss below).
So, in lecture/index.ts, put a name property on your routes:
import { Router, RouterConfiguration } from 'aurelia-router';
export class Index {
configureRouter(config: RouterConfiguration, router: Router) {
config.options.pushState = true;
config.map([
{ route: '', name: 'lecture-list', moduleId: './list' },
{ route: ':id', name: 'lecture-detail', moduleId: './details' }
]);
}
}
And lastly, instead of having a click event in your view which is calling router.navigate, you can use the route-href attribute which will allow you to make links work with the router. So, something like the following:
<template>
<div repeat.for="lecture of lectures" class="grid-body-cell">
<a route-href="route: lecture-detail; params.bind: { id: lecture.id }">${lecture.title}</a>
</div>
</template>
Notice how we are referencing our newly named route by its name, lecture-detail? Now Aurelia will come through and parse our link and update the href property to go where it needs too.
Hope that helps.

app.html
<template><router-view></router-view></template>
app.ts
import { Router, RouterConfiguration } from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration, router: Router) {
config.pushState = true;
config.map([
{
moduleId: './lectures/index',
name: 'lectures',
nav: true,
route: ['', 'lectures/*id'],
title: 'Lectures'
}
]);
};
}
lectures/index.html
<template><router-view></router-view></template>
lectures/list.html
<template>
<div repeat.for="lecture of lectures" class="grid-body-cell" click.delegate="navigateToLecture(lecture)">
${lecture.title}
</div>
</template>
lectures/list.ts
import { autoinject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
#autoinject()
export class LecturesList {
constructor(private router: Router) {}
navigateToLecture(lecture: {id:number}) {
this.router.navigateToRoute(`lectures`, { id: lecture.id });
}
}
This took me hours of banging my head against the wall and I'm not even sure this will continue to work when I add more routes to the lectures/index.ts, but it works for now and allows me to continue on.

Related

Making request to Spring Boot Admin server from custom view?

I'm trying to add a custom view with some administrative utilities to Spring Boot Admin. The idea is to implement these as endpoints in Springboot Admin and call these endpoints from my custom view, but I don't know how to make a call to the server itself.
When a custom view has parent: 'instances' it will get an axios client for connecting to the current instance, but since the view I'm building isn't tied to a specific instance it doesn't have this. I'm aware I can install axios as a dependency, but I'd like to avoid that if possible to reduce build times. Since SBA itself depends on axios it seems I shouldn't have to install it myself.
Based on this sample, this is what I have right now:
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: {
type: Array,
required: true
}
},
data: () => ({ exampleResponse: "No response" }),
async created() {
const response = await this.axios.get("example");
this.exampleResponse = response.response;
},
};
</script>
ExampleController.kt
#RestController
#RequestMapping("/example")
class ExampleController {
#GetMapping
fun helloWorld() = mapOf("response" to "Hello world!")
}
Console says that it can't read property get of undefined (i.e. this.axios is undefined). Text reads "GET /example: No response"
I'm not sure if this is the best way to do it, but it is a way.
I noticed that I do have access to the desired axios instance within the SBA.use { install(...) { } } block, and learned that this can be passed as a property down to the view.
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry, axios}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
// this is where we pass it down with the props
// first part is the name, second is the value
props: { "axios": axios },
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: { type: Array, required: true },
// this is where we retrieve the prop. the name of the field should
// correspond to the name given above
axios: { type: Object, required: true },
},
data: () => ({
exampleResponse: "No response",
}),
async created() {
// Now we can use our axios instance! And it will be correctly
// configured for talking to Springboot Admin
this.axios.get("example")
.then(r => { this.exampleResponse = r.data.response; })
.catch(() => { this.exampleResponse = "Request failed!" });
},
};
</script>
Based on the code given, it looks like you don't have axios initialized to how you want to use it.
You're calling it via this.axios but it's not in your component i.e
data() {
return {
axios: require("axios") // usually this is imported at the top
}
}
or exposed globally i.e
Vue.prototype.axios = require("axios")
You can simply just import axios and reference it.
<script>
import axios from 'axios';
export default {
created() {
axios.get()
}
}
</script>

Vue3 - after provide/inject object inside component is loosing data

I'm having small issue with provide/inject in my project.
In App.vue, I'm pulling data from DB and pushing it into object. With console log I checked and all data it's there.
<template>
<router-view />
</template>
<script>
export default {
provide() {
return {
user: this.user,
};
},
data() {
return {
user: '',
};
},
methods: {
///pulling data from DB
func() {
fetch("url")
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => {
const user = [];
for (const id in data) {
user.push({
id: data[id].user_id,
firstName: data[id].user_firstname,
lastName: data[id].user_lastname,
email: data[id].user_email,
phone: data[id].user_phone,
address1: data[id].user_address_1,
address2: data[id].user_address_2,
address3: data[id].user_address_3,
address4: data[id].user_address_4,
group: data[id].user_group,
});
}
this.user = user;
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.func();
},
};
</script>
Console log of object user App.vue
Object { id: "3", firstName: "test", lastName: "test", … }
Next I'm injecting it into component. Object inside component exists, but empty - all data cease to exist.
<script>
export default {
inject: ["user"],
};
</script>
console log of object user in component
<empty string>
While in App.vue data is still there, in any components object appears to be empty, but it is there. Any idea why?
Thanks for help.
In short, this happens because you are reassigning user rather than changing user.
Let's say you have a Child component that consumes your inject data and renders the users in a list:
<template>
<div> Child </div>
<ul>
<li v-for="user in users" :key="user.id"> {{user.name}} </li>
</ul>
</template>
<script>
import {inject} from "vue";
export default {
name: "Child",
setup() {
const users = inject("users");
return {users};
}
}
</script>
To provide the users from parent component, all you need to ensure is that users itself is a reactive object, and you keep changing it from the parent rather than reassigning it.
I am going to use the composition api to illustrate what I mean. Compared to options api, everything in composition api is just plain javascript hence there is a lot less behind-the-scene magic. At the end I will tell you how options api is related to the composition api.
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {reactive, provide, toRefs} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const data = reactive({users: ""});
const generateUsers = () => {
// notice here you are REASSIGNING the users
data.users = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(data.users);
}
// this way of provide will NOT work
provide("users", data.users);
// this way works because of toRefs
const {users} = toRefs(data);
provide("users", users);
return {generateUsers};
}
}
</script>
A few things to note:
the data options in the options api is exactly the same as const data = reactive({users: ""}). Vue will run your data() method, from where you have to return a plain object. And then Vue will automatically call reactive to add reactivity to it.
provide, on the other hand, is not doing any magic - neither in options api, nor in the composition api. It just passes whatever it is given to the consuming component without any massaging.
the reason provide("users", data.users) does not work as you would expect is that the way you populate the users is not a change to the same data.users object (which actually is reactive), but a reassign all together.
the reason toRefs works is because toRefs links to the original parent.
With this understanding in mind, to fix your original code, you just need to ensure you change, instead of reassigning, the users. The simplest way is to define user as an array and push into it when you load data. (in contrast to defining it initially as a string and reassigning it later)
P.S. what also works in composition api, and is a lot simpler, is to:
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {ref, provide} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const users = ref();
const generateUsers = () => {
// notice here you are not reassigning the users
// but CHANGING its value
users.value = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(users.value);
}
provide("users", users);
return {generateUsers};
}
}
</script>

How should import be used in a single page Vue component?

I have trouble importing an external class into a single file Vue component.
The component looks like this:
<style>
</style>
<template>
<div>
A page
</div>
</template>
<script>
import AClass from './js/aclass.js';
// -----------------------------------------
// I have also tried all the below options
// -----------------------------------------
// import default from '/js/aclass.js';
// import * from '/js/aclass.js';
// import '/js/aclass.js';
// import default from '/js/aclass';
// import AClass from '/js/aclass';
// import * from '/js/aclass';
// import { AClass } from '/js/aclass.js';
// const AClass = require('/js/aclass.js');
module.exports = {
data: function () {
return {
}
},
mounted: function(){
// console.log('ACLass', AClass);
},
destroyed: function(){
},
methods: {
},
}
</script>
and the class file looks like this:
class AClass {
constructor() {
return this
}
}
export AClass
or
export default class AClass {
constructor() {
return this
}
}
None of the options seem to work: all the options based on import generate an error that says ReferenceError: "responseText is not defined" and the ones based on require one that says ÀClass is not defined` (when the console.log line is uncommented).
I have resorted to loading the class in the window global in the Vue.js base app - but everyone says that's barbaric, and I would like to be able to delay the loading of the library (the real one is heavier, and somewhat rarely used).
I have checked the various answers such as this and this and this but it looks like the mere use of import in the component makes things go wrong.
The component itself is loaded like this
var app = new Vue({
el: '#app',
router: (new VueRouter({
routes: [
{ path: '/view/grid/:md5', component: httpVueLoader('components/view/grid.vue'), name: 'grid_md5' },
]
})),
});
I am sure the solution must be there somewhere, but after about three hours of banging my head against this, I will throw myself at the mercy of the StackOverflow crowd.

Aurelia: How to access data stored in a child-router's view-model

I am fairly new to Aurelia and I am trying to understand what’s the best way to access and display data in a subpage of a child-router. The data is stored in the activate method of the child-router’s view-model. My Problem is to display the data when I first enter or reload a subpage of the child-router. Unfortunately it's not working. As soon as I have displayed a subpage and go to another, it all works fine.
child-router.js
export class ChildRouter {
configureRouter(config, router) {
config.title = 'Child Router Title';
config.map([
{ route: '', redirect: 'basics'},
{ route: ['basics', ''], name: 'basics', moduleId: './basics/basics', nav: true, title: 'Basics' },
{ route: 'data', name: 'data', moduleId: './data/data', nav: true, title: 'Data' }
]);
this.router = router;
}
activate() {
this.var1 = "Var1. Only works when I reenter a subpage.";
this.var2 = "Var2. Only works when I reenter a subpage.";
}
}
child-router.html
<template>
<require from="../components/detail-navigation.html"></require>
<h2>${router.title}</h2>
<detail-navigation router.bind="router"></detail-navigation>
<div class="page-host">
<router-view></router-view>
</div>
</template>
basics.html
<template>
<h2>Basics Title</h2>
<h3>${var1}</h3>
</template>
data.html
<template>
<h2>Data Title</h2>
<h3>${var2}</h3>
</template>
I hope you understand my problem.
Here is a link to a test projekt on git.
I am looking forward for any recommendations.
Personally, I would really recommend you not try to do this. This is introducing tight coupling between the ChildRouter page and any pages displayed as routes on it. If you need these pages to talk to each other, consider using the Dependency Injection provider to inject an instance of the same class in to each page and sharing information that way.

Aurelia parameterized routing outside navbar

I have a navbar with the router links like in the Aurelia skeleton, but I want to also have parameterized links inside the router-view that change the router-view. Is this possible? If so, how do I access the router which is on App.ts? Any help is appreciated, thanks.
You just have to inject the router in your view-model. Like this:
import { autoinject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
#autoinject
export class MyScreenInRouterView {
constructor(router: Router) {
this.router = router;
}
}
To use it in your view:
<a route-href="route: routeName; params.bind: { id: user.id }">${user.name}</a>
To generate URLs in code:
this.url = this.router.generate('routeName', { id: 123 });
To navigate to a route:
this.router.navigateToRoute('routeName', { id: 123 });
More information at http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/cheat-sheet/7