How to maintain state in view models between routes in Aurelia? - aurelia

I have a Menu and Search module. When I navigate between Menu and Search, I want to preserve my search results and Search.js state. I want modules to load via the router like a desktop application where state is maintained between module 'windows'.
App.html
<template>
<router-view></router-view>
</template>
Search.js
import {inject} from "aurelia-framework";
import {PortalData} from "./portalData";
import $ from 'jquery';
#inject(PortalData)
export class Search
{
constructor(portalData){
this.portalData = portalData;
this.criteria = "";
this.SearchResults = [];
}
DoSearch(startRow){
this.portalData.searchSevadars(criteria)
.then(res=> this.SearchResults = res;
}
}
Menu.js
import {inject} from "aurelia-framework";
import {PortalData} from "./portalData";
#inject(PortalData)
export class Start {
constructor(portalData){
this.portalData = portalData;
}
activate(){
return this.portalData.getApplications()
.then(apps => this.applications = apps);
}

The most obvious solution is to store state in another module.
Import some class in both views, then on search store it inside a property of that class.
By default aurelia uses singleton for injected classes, so you will have a shared instance between your views.
somestate.js
export class SomeState {
constructor(){
this.data = undefined;
}
}
import this module in both.
use data property to share data between modules;

Create a file called core.js (or something else of your choosing) in the root application folder /src with something like the following. I added some extra things here to make it more realistic but you could simplify it to meet your specific needs. My point is that this singular class could be used for a LOT of different things -- search text being just one of them.
/src/core.js
// Some example imports to support the common class
import { inject, noView } from 'aurelia-framework';
import { HttpClient, json } from 'aurelia-fetch-client';
import { I18N } from 'aurelia-i18n';
import { EventAggregator } from 'aurelia-event-aggregator';
#noView // this decorator is needed since there is no core.html
#inject(EventAggregator, I18N, HttpClient)
export class Core {
value1 = "Test data 1";
value2 = "Test data 2";
constructor(eventAggregator, i18n, httpClient) {
// store local handles
this.eventAggregator = eventAggregator;
this.i18n = i18n;
this.httpClient = httpClient;
// search info
this.SearchResults = [];
}
myCustomFunction() {
// some code here, available to any component that has core.js injected
}
}
Then, import and inject core.js into each of your other components, like this:
search.js
import {inject} from "aurelia-framework";
import {PortalData} from "./portalData";
import {Core} from "core";
import $ from 'jquery';
#inject(PortalData, Core)
export class Search
{
constructor(portalData, core){
this.portalData = portalData;
this.core = core;
this.criteria = "";
}
DoSearch(startRow){
this.portalData.searchSevadars(criteria)
.then(res=> this.core.SearchResults = res;
}
}

Related

How to use my own class inside a Vue file

I'm making a webpage using Nuxt and I would like to make a class and use it in one of my .vue files. I've tried using an import: import Card from "~/assets/mylib/Card.js" but that doesn't work. Not sure how to access my Card.js file inside of a .vue file.
index.vue
import Card from "~/assets/mylib/Card.js"
created() {
let card = new Card("blue")
}
Card.js
class Card {
constructor(color) {
this.color = color
}
}
error:
_assets_mylib_Card_js__WEBPACK_IMPORTED_MODULE_4___default.a is not a constructor
Modify Card.js as follows:
export default class Card {
constructor(color) {
this.color = color
}
}
Then import it from within index.vue as follows:
import { Card } from "~/assets/mylib/Card"
you have to update your Card.js like beow
export class Card {
constructor(color) {
this.color = color
}
}
and import in vue file like below
import { Card } from "~/assets/mylib/Card"

Creating reusable getters in vuex-module-decorators

Faced such a problem using vuex-module-decorators. I wanted to create some parent module so child modules could be extended from it and inherit its actions and getters. But getters are not inherited.
I tried it that way:
My parent-module.ts:
import {Action, getModule, Module, Mutation, VuexModule} from 'vuex-module-decorators';
export class ParentStore extends VuexModule {
public get getterForInherit(): any {
return someData
}
}
Child modules:
child-one.ts:
import {Action, getModule, Module, Mutation, VuexModule} from 'vuex-module-decorators'
import {ParentModule} from './parent-module';
#Module({dynamic: true, store: Store, name: 'childOne', namespaced: true})
class FirstChildModule extends ParentModule {
public get SecondChildGetter(): number {
return 1;
}
}
export const FirstChildStore: ParentModule = getModule(FirstChildModule)
child-two.ts:
import {Action, getModule, Module, Mutation, VuexModule} from 'vuex-module-decorators'
import {ParentModule} from './parent-module';
#Module({dynamic: true, store: Store, name: 'childTwo', namespaced: true})
class SecondChildModule extends ParentModule {
public get FirstChildGetter(): number {
return 2;
}
}
export const SecondChildStore: ParentModule = getModule(SecondChildModule)
But when I import those modules to components getterForInherit is not available. Is it possible to do it this way?
I think instead of trying to use a different package for handling your mutations, actions and getters;
From your question I assume you want to be able to access your getters and actions either from the parent or child component. You can use vuex if you already have it installed.
You can do something like this:
import { mapGetters, mapActions, mapMutations } from 'vuex'
methods: {
...mapActions(['submitTransaction', 'submitNotification']),
...mapMutations(['clearNotificationData']),
},
computed: {
...mapGetters([
'messageData',
'isMessageLoaded',
'isProcessingRequest',
]),
I had same problem and found a solution with "vuex-class-modules" packet. It have similar decorators.
export class LoadItems extends VuexModule {
public items = [];
#Mutation
public SET_ITEMS(...
#Action
public getItems() {
this.SET_ITEMS(['hello', 'world'])
}
After you extend this class in your child:
#Module
class Contract extends LoadItems {
// your additional code here
}
export const ContractModule = new Contract({ store, name: "contract" });
Now you can get any statements and call any action with command:
ContractModule.getItems();
Of course, you have to import it before.

Dynamic & Namespaced modules not registered (vuex-module-decorators, vuex-class)

The module is not registered:
$nuxt.$store._modulesNamespaceMap // No properties
Also, i'm getting this warning:
Classic mode for store/ is deprecated and will be removed in Nuxt 3.
What i've tried:
loading manually the module to see if the namespace was working (FAIL).
Try without namespace to load dynamically the module (FAIL).
Code:
// #/store/modules/User.ts
import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators";
import { firebase, auth, GoogleProvider, StoreDB } from "#/services/fireinit";
import { IUser } from "~/types/user";
import { store } from "..";
// It seems like the "store" is messed somehow
#Module({ dynamic: true, namespaced: true, store: store, name: "user" })
export default class User extends VuexModule {
user: IUser = null;
#Action
async autoSignIn(user: IUser) {
this.context.commit("setUser", user);
}
#Action
async signInWithGoogle() {
return new Promise(resolve => {
console.log("signInWithGoogle:", this);
auth.signInWithRedirect(GoogleProvider);
resolve();
});
}
// trunked ...
}
// #/store/index.ts
import Vuex, { Store } from "vuex";
import User from "./modules/User";
// trunked ...
// Declare empty store first
export const store = new Vuex.Store<IStoreType>({});
const createStore = () => store;
export default createStore;
// #/pages/login.vue
// trunked ...
const User = namespace('user'); // [vuex] module namespace not found in mapActions(): user/
#Component({})
export default class LoginComponent extends Vue {
#User.State("activeUser") stateUser;
#User.Action("signInWithGoogle") actionSignInWithGoogle;
}
Tree:
├── pages
│ └── login.vue
├── store
│ ├── index.ts
│ └── modules
│ └── User.ts
I expect to be able to load dynamically & namespaced modules...
I tried everything i could find on the world wide web but i can't manage to make it work.
What i'm doing wrong ?
Ok, i found a way that work...
Code:
// #/store/index.ts
import Vuex, { Store } from "vuex";
import User from "./modules/User";
// trunked ...
// Declare empty store first
export const store = new Vuex.Store<IStoreType>({});
// No more "createStore" shit.
// #/pages/login.vue
// trunked ...
const User = namespace('modules/User/'); // "modules/" is important to make it work.
#Component({})
export default class LoginComponent extends Vue {
#User.State("activeUser") stateUser;
#User.Action("signInWithGoogle") actionSignInWithGoogle;
}

InversifyJS : can not bind using interface

I am trying to use inversify JS to inject dependencies on TypeScript App. I started by using the example of https://github.com/inversify/InversifyJS page :
// file interfaces.ts
interface Warrior {
fight(): string;
}
// file types.ts
const TYPES = {
Warrior: Symbol("Warrior")
};
export { TYPES };
// file entities.ts
import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Warrior } from "./interfaces"
import { TYPES } from "./types";
#injectable()
class WarriorImpl implements Warrior {
public constructor(){
}
public fight() { return "I fight"; }
}
export { WarriorImpl };
// file inversify.config.ts
import { Container } from "inversify";
import TYPES from "./types";
import { Warrior } from "./interfaces";
import { WarriorImpl } from "./entities";
const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(WarriorImpl);
export { myContainer };
I applied what is provided in the example but Vscode and tsc failed at the binding line by showing this error [ts] Untyped function calls may not accept type arguments. [ts] Cannot find name 'Warrior'.
Your example is clean and correct all you have to do is to upgrade your typescript version in your package.json
"devDependencies": {
"typescript": "3.9.3"
}
You have to export the interface for it to be imported:
// file interfaces.ts
export interface Warrior {
fight(): string;
}
Your import in inversify.config.ts should be:
import { TYPES } from "./types";
Make sure you have actually installed the library (npm install inversify). The error Untyped function calls may not accept type arguments is sue to missing type information.

Using withMessageKey for custom validation messages in Aurelia

I have an Aurelia app and I am using the Aurelia validation tool for client-side validation. I want to use the validationMessages dictionary to define a list of custom validation messages to use throughout my app using withMessageKey like so:
import {validationMessages} from 'aurelia-validation';
validationMessages['customMessage1'] = `My first custom message`;
validationMessages['customMessage2'] = `My second custom message`;
And then when I set the validation rules on the class:
import { ValidationRules } from "aurelia-validation";
export class SampleObject {
text1;
text2;
constructor() {
ValidationRules
.ensure(a => a.text1)
.required()
.then().satisfies(x => x.trim() === x)
.withMessageKey('customMessage1')
.ensure(a => a.text2)
.satisfies( x => x.length > 5)
.withMessageKey('customMessage2')
.on(this);
}
};
The validation works, but the custom messages do not show up, the standard ones do. If I use withMessage('My first custom message') for example instead, then it does work, but I want to keep all of my custom messages in one place for use throughout the app.
What am I doing wrong?
Here is my solution:
I created a class which contains my custom messages in the constructor:
import { validationMessages } from 'aurelia-validation';
export class CustomValidationMessages {
constructor() {
validationMessages['customMessage1'] = `My first custom message`;
validationMessages['customMessage2'] = `My second custom message`;
}
}
Then, I inject it into my app.js:
import { inject } from 'aurelia-framework';
import { CustomValidationMessages } from "resources/utils/validation-messages";
#inject( CustomValidationMessages )
export class App {
constructor() {
}
configureRouter(config, router) {
.....
}
}
And I am able to use customMessage1 and customMessage2 everywhere throughout my app. I'm not sure this is the best way to do this, but it works.