Is there a way to import additional variables/data from the dialog-service to the controller?
For example I have an array of possible options in a form of my app-view. I fetch the data via an API from a server.
I'd like to edit an entry with an aurelia-dialog and don't want to fetch the data again to avoid unnecessary traffic in my app.
How can i pass the array additionally to the model. Pack it all together in an Object and unwrap it in the controller?
As far as I know the activate-method of the controller only takes one argument, doesn't it?
Thank you
Isn't the example in the repository exactly what you are looking for?
The person attribute is passed to the dialog service via the settings object (model: this.person). This may be data you fetched from the server. As you mentioned, you can of course add multiple objects to the model as well which will be available in the activate() method of your dialogs vm.
import {EditPerson} from './edit-person';
import {DialogService} from 'aurelia-dialog';
export class Welcome {
static inject = [DialogService];
constructor(dialogService) {
this.dialogService = dialogService;
}
person = { firstName: 'Wade', middleName: 'Owen', lastName: 'Watts' };
submit(){
this.dialogService.open({ viewModel: EditPerson, model: this.person}).then(response => {
if (!response.wasCancelled) {
console.log('good - ', response.output);
} else {
console.log('bad');
}
console.log(response.output);
});
}
}
Related
I have a Controller (nestjs / routing-controllers) and I'm passing a GET request the following way: /collect?t=My-name
t is actually a full name which I can't change.
Bellow im injecting #QueryParams() data: CollectData, Im looking for a way (like java strong and .net) to tell the controller that fullName is actually t.
Something like
export interface CollectData{
#PropertyName('t')
fullName: string
}
I'm expecting fullName to represent the t variable.
#JsonController()
export class CollectController {
#Get('/collect')
collect(#QueryParams() data: CollectData){
return data.fullName;
}
}
You could use some sort of solution using the class-transformer library and the ValidationPipe given by Nest (it also does transformations!) and have your CollectionData class (use a class so that the data can be serialized after transpiling, interfaces go away in JavaScript) look like this:
// CollectData.ts
export class CollectData{
#Expose({ name: 'fullName' })
t: string
}
//Collect.controller.ts
#Controller() // Changed from JSONController to Controller
export class CollectController {
#Get('/collect')
collect(#Query(new ValidationPipe({ tranform: true }) data: CollectData){ //Changed from QueryParams to Query
return data.fullName;
}
}
OR in your main.ts add the app.useGlobalPipes(new ValidationPipe({ tranform: true }) to set the validation pipe to run against all your incoming requests
referring to the following post StackOverflow Question I have a quite different scenario where I want to know if Aurelia has a solution for.
Scenario:
I have a user model:
export class User{
#bindable name: string;
#bindable address: Address
As you can see, "Address" is a sub-model.
I have a main view-model "registration". In this view model I have a model "user":
export class RegistrationView{
#bindable user: User
public attached(){
this.user = userService.fetchUserFromApi();
}
In addition to that I have a custom-element "user-address" where I have a "user-address"-model (because I want to have dedicated encapsulated custom-elements).
export class userAddress{
#bindable userAddress: Address
Now I want to request the user model only once from the API and send the user address it to the custom-element:
<template>
<require from="user-address"></require>
<user-address user.address.bind="${dj.address}"></user-address>
Finally I would (to have dedicated encapsulated custom-elements that I can use everywhere) check in attached method if the user is already load and if not then the custom-element would load all needed data:
export class userAddress{
#bindable userId: string
#bindable address: Address
public attached(){
if(!(typeof this.address === "undefined")){
this.address = this.addressAPIService.getAddressByUserId(id)
}
}
Problem 1: I know, that the mentioned template dj.address.bind doesn't work. But now my question is, how can I handle that situation?
Problem 2: How do I assure, that the user object is only requested once?
Does my concept makes sense and does it is the idea of Aurelia?
If I understand your problem correctly, you simply need some form of client-side persistence.
If you need this persistence even after the user closed the browser, you'll want to use either localStorage or some encapsulation thereof. There are many good plugins available such as localForage, LokiJS and a recently developed (still in beta) aurelia plugin aurelia-store
You probably want to encapsulate the retrieval of your user in a UserService of some sort. This is nothing specific to Aurelia, just generally how you want to do this in most types of applications.
Example
So in your viewmodel you might have something like this (skipping some of the implementation details such as checking the params, configuring the router etc for brevity):
#autoinject()
export class UserViewModel {
public user: User;
constructor(private userService: UserService){}
// happens before bind or attached, so your child views will always have the user in time
public async activate(params: any): Promise<void> {
this.user = await this.userService.getUserById(params.id);
}
}
And in your userservice:
// singleton will ensure this service lives as long as the app lives
#singleton()
export class UserService {
// create a simple cache object to store users
private cache: any = Object.create(null);
constructor(private client: HttpClient) {}
public async getUserById(id: number): Promise<User> {
let user = this.cache[id];
if (user === undefined) {
// immediately store the user in cache
user = this.cache[id] = await this.client.fetch(...);
}
return user;
}
}
Let your view model just be dumb and call the UserService whenever it needs to load a user, and let your service be clever and only fetch it from the API when it's not already cached.
I'd also like to point out that attached() is not when you want to be grabbing data. attached() is when you do DOM stuff (add/remove elements, style, other cosmetic things). bind() is best restricted to grabbing/manipulating data you already have on the client.
So when to fetch data?
In your routed view models during the routing lifecycle. That'll be configureRouter, canActivate, activate, canDeactivate and deactivate. These will resolve recursively before any of the DOM gets involved.
Not in your custom elements. Or you'll soon find yourself in maintenance hell with notification mechanisms and extra bindings just so components can let eachother know "it's safe to render now because I have my data".
If your custom elements can assume tehy have their data once bind() occured, everything becomes a lot simpler to manage.
And what about API calls invoked by users?
More often than you think, you can let an action be a route instead of a direct method. You can infinitely nest router-views and they really don't need to be pages, they can be as granular as you like.
It adds a lot of accessibility when little sub-views can be directly accessed via specific routes. It gives you extra hooks to deal with authorization, warnings for unsaved changes and the sorts, it gives the user back/forward navigation, etc.
For all other cases:
Call a service from an event-triggered method like you normally would during activate(), except whereas normally the router defers page loading until the data is there, now you have to do it yourself for that element.
The easiest way is by using if.bind="someEntityThatCanBeUndefined". The element will only render when that object has a value. And it doesnt need to deal with the infrastructure of fetching data.
I am using spring data rest. When I try to create a resource using post method with application/json using following object, association resources are not binded although they are already present in db
{
screeName : 'adsaf',
screenType : {
screenTypeId : 1,
screenTypeName : 'Fixed'
}
}
Why? Is there anyother way of accomplishing this task other than separately setting associations? I am asking this question because if I manually receive this form in a controller and use ObjectMapper to deserialize and then save this object, all associations would be set. Then why its not happening in spring data rest
Spring Data REST works with links to resources so you have to change your payload to something like this:
POST http://localhost:8080/api/screens
{
"screenName": "adsaf",
"screenType": "http://localhost:8080/api/screenTypes/1"
}
If you need to save ScreenType when you POST Screen object too, you should turn off the exporting of your ScreenType repository:
#RepositoryRestResource(exported = false)
public interface ScreenTypeRepo extends JpaRepository<ScreenType, ...> {
}
and add cascading (at least PERSIST) to your screenType field in Screen entity:
public class Screen {
//...
#ManyToOne(cascade = CascadeType.PERSIST)
ScreenType screenType;
}
That's mean that ScreenType will be managed by Screen. In this case you would be able to use a payload like this:
POST http://localhost:8080/api/screens
{
"screenName": "adsaf",
"screenType": {
"screenTypeName": "Fixed"
}
}
to create a new ScreenType simultaneously with Screen.
This can be done using Custom HttpMessageConverter. check following thread.
Creating Resource with references using spring data rest
DISCLAIMER: I'm a noob.. sorry
Say I have 2 different components that are siblings:
comp1 and comp2
I wish to route from comp1 to comp2 with a bunch of data. How can I achieve this without getting a fugly url-bar containing everything?
I've tried using a separate class, lets call it DataTransmitter:
data-transmitter.js:
export class DataTransmitter {
constructor() {
this.val= "a";
}
}
comp1.js:
import { DataTransmitter } from './data-transmitter';
#inject(DataTransmitter)
export class comp1{
constructor(DataTransmitter){
this.DataTransmitter = DataTransmitter;
}
someMethod(){
this.DataTransmitter.val = "b";
console.log('comp1: ' + this.DataTransmitter.val);
}
}
comp2.js:
import { DataTransmitter } from './data-transmitter';
#inject(DataTransmitter)
export class comp2{
constructor(DataTransmitter){
this.DataTransmitter = DataTransmitter;
}
someMethod(){
console.log('comp2: ' + this.DataTransmitter.val);
}
}
This gives me the output:
comp1: b
comp2: a
I've also tried messing around with EventAggregator, but no success.
Is there some way of routing with parameters WITHOUT having a url that looks like site/comp2?data=stuff&things=otherstuff¶ms=values&more=etc?
You absolutely want to use a singleton class and then inject it inside of whatever components you need your data. The link that Gaby posted is definitely what you want to do.
The reason your posted code does not work is because you're attempting to use the inject decorator, but you're not importing it. Please see this working example of what you are trying to do on Gist.run here. I have two components, you can click to route between them and set the value. You'll notice the set value remains when you navigate back and forth.
I have a custom element called summary-bar with summary property:
export class SummaryBarCustomElement {
#bindable summary;
---
In another component test-website, I uses the summary-bar element and bind its data as below:
<summary-bar summary.bind="testWebsiteSummary"></summary-bar>
And here testWebsiteSummary is defined in the test-website.js ViewModel:
export class TestWebsiteCustomElement {
testWebsiteSummary = {
passed_result_count: 0,
failed_result_count: 0,
incomplete_result_count: 0,
unknown_result_count: 0
}
---
There are several functions in TestWebsiteCustomElement class that modify the values of testWebsiteSummary.passed_result_count, testWebsiteSummary.failed_result_count, testWebsiteSummary.incomplete_result_count and testWebsiteSummary.unknown_result_count. However, the summary-bar element is not reloaded with the new values of testWebsiteSummary. Is there a way to achieve that? What I mean is every time the properties of testWebsiteSummary is updated, is it possible to update the summary-bar with the new values? Thank you.
Example of a function which changes the properties:
changeWebsiteSummary(status) {
switch (status) {
case "SUCCESS":
this.testWebsiteSummary.passed_result_count++;
this.testWebsiteSummary.incomplete_result_count--;
break;
case "INCOMPLETE":
this.testWebsiteSummary.incomplete_result_count++;
this.testWebsiteSummary.passed_result_count--;
break;
default:
}
}
When you bind an object into your Custom Element it will update its values automatically. Whenever your TestWebsiteCustomElement changes any of the properties in testWebsiteSummary, those changes will be automatically reflected in your SummaryBarCustomElement. That is, if you are for example displaying testWebsiteSummary.passed_result_count in the SummaryBarCustomElement view, then it will be automatically updated in the ui.
Now, if what you want is to know when those changes occur to do something else, then you need to use a propertyObserver.
Aurelia by default support adding methods such as summaryChanged(newValue, oldValue) to custom elements. This works just fine for primitive values, but for Objects (or arrays) this method will not be triggered if any of the internal properties changes, only if the object itself has been reassigned.
To work around this you can use the binding engine to observe specific properties inside your summary object. Here is what it would look like:
import {bindable, BindingEngine, inject} from 'aurelia-framework';
#inject(BindingEngine)
export class SummaryBarCustomElement {
#bindable summary;
constructor(bindingEngine){
this.bindingEngine = bindingEngine;
}
bind(){
this.subscription = this.bindingEngine.propertyObserver(this.summary, 'passed_result_count')
.subscribe(newValue, oldValue => this.passedResultCountChanged(newValue, oldValue))
}
detached(){
this.subscription.dispose();
}
passedResultCountChanged(newValue, oldValue){
//Do something
}
}
You can use the signal binding behaviour
<summary-bar summary.bind="testWebsiteSummary & signal:'your-signal'"></summary-bar>
And the class:
import {BindingSignaler} from 'aurelia-templating-resources';
export class TestWebsiteCustomElement {
constructor(signaler: BindingSignaler) {
this.signaler = signaler;
}
functionThatChangesValues(){
this.signaler.signal('your-signal');
}
}