I'd like to preference this by saying my backgrounds in in C# so I like declaring methods within my classes. I've created a user class that contains properties and methods and I've added this to my vuex-persistedstate. One of the methods is a logout() method which clears out the properties. When I tried to invoke this method I got the following error:
TypeError: this.$data.user.logout is not a function
I then reviewed local storage and noted the user did not have reference to the class method. So I went ahead and copied the logic from the method into my vue component and it worked so I'm assuming the issue is vuex-persistedstate does not save references to methods which is why the method call did not work.
I'd like to declare the logout method in one location rather than spreading it out across vue components, what is the best practice for accomplishing this? Is it possible to do this in the class declaration or do I need a user helper file?
Sure Berco! My code is also up on GitHub so you can review it there too, but basically it seems to me that vuex does not store methods. The first file you should review is my user.js file:
https://github.com/Joseph-Anthony-King/SudokuCollective/blob/master/SudokuCollective.WebApi/client/src/models/user.js
In this file I have a method called shallow clone which takes the info received from the API and assigns it to the user:
shallowClone(data) {
if (data !== undefined) {
this.id = data.id;
this.userName = data.userName;
this.firstName = data.firstName;
this.lastName = data.lastName;
this.nickName = data.nickName;
this.fullName = data.fullName;
this.email = data.email;
this.isActive = data.isActive;
this.isAdmin = data.isAdmin
this.isSuperUser = data.isSuperUser;
this.dateCreated = data.dateCreated;
this.dateUpdated = data.dateUpdated;
this.isLoggedIn = data.isLoggedIn;
}
}
You of course don't need to abstract this away but I've found it makes the code easier to maintain.
Then in the mounted() lifecycle hook I assign the user received from the API to the component user via the shallowClone method. Please bear in mind I've done additional work on this project and the login form is now it's own component which receives the user as a prop from the app:
https://github.com/Joseph-Anthony-King/SudokuCollective/blob/master/SudokuCollective.WebApi/client/src/components/LoginForm.vue
mounted() {
let self = this;
window.addEventListener("keyup", function (event) {
if (event.keyCode === 13) {
self.authenticate();
}
});
this.$data.user = new User();
this.$data.user.shallowClone(this.$props.userForAuthentication);
},
The full code can be reviewed here:
https://github.com/Joseph-Anthony-King/SudokuCollective
I found a solution... I'm working on improving it. Basically I use the values pulled from localstorage into vuex to create a new user object in the vue component that has reference to the methods located in my user class declaration. I recalled recommendations that we should create clones of objects pulled from vuex for use within the vue component. I'm still refining the code but that's basic idea.
Related
I need to use an API that requires initialization with an API key and some other details within my Vue.js app.
var client = api_name('app_id', 'api_key', ...)
I would need to make several API calls with the client object in multiple components in my app
client.api_function(...)
How can I avoid repeating the initialization step in every component?
I'm thinking about using a global mixin in main.js for that
Vue.mixin({
data: function() {
return {
get client() {
return api_name('app_id', 'api_key');
}
}
}
})
Is this a good approach?
I'd rather move your getter to a service and just import, where you actually need it. It doesn't seem to fit into data section, more like methods. A mixin is a decent approach if you need lots of similar stuff: variables, methods, hooks etc. Creating a mixin for only 1 method looks like overkill to me.
// helper.js
export function getClient () {
// do stuff
}
// MyComponent.vue
import { getClient } from 'helpers/helper`
// Vue instance
methods: {
getClient
}
How about creating a helper file and writing a plugin that exposes your api url's? You can then create prototypes on the vue instance. Here's an example,
const helper = install(Vue){
const VueInstance = vue
VueInstance.prototype.$login = `${baseURL}/login`
}
export default helper
This way you can access url's globally using this.$login. Please note $ is a convention to avoid naming conflicts and easy to remember that it is a plugin.
I am trying to separate out some code that is common among many calls in my Vuex mutations. I am getting the feeling that this is discouraged but I don't understand why.
Have a look at an image of some sample code below:
I have added this 'helpers' entry in the Vuex - this obviously doesn't exist but how can I call the shared helper function 'getColumn' from mutations and/or actions?
Or do I have resort to calling a static method on a 'VuexHelper' class? :(
Something like:
Note
I have already looked at the following:
Vue Mixins - yes, something like that could work but is not
supported in Vuex - also, vue methods don't return a value...
I have looked at Modules but these still don't give me what I need, i.e. a simple re-usable function that returns a value.
Thanks
I don't see why you may want to put the helper function within the store. You can just use a plain function.
function getColumn(state, colName) {
// Do your thing.
}
const vstore = new Vuex.Store({
// ....
mutations: {
removeColumn(state, colName) {
var column = getColumns(state, colName);
}
}
};
On the other hand, if you really need that, you can access the raw module and all that's included:
var column = this._modules.root._rawModule.helpers.getColumns(state, colName);
Although this syntax is not documented and can change for later versions.
You can implement your Vuex getter as a method-style getter. This lets you pass in the specific column as an argument:
getters: {
getColumn: state => colName => {
return state.columns[colName] || null
}
}
Then getColumn can be used within the store like so:
let column = getters.getColumn('colNameString')
vuex docs > getters > method style access
I'm using react js with mobx and I get data from api.
the data I get is array of objects.
when I set the data into mobx variable then I see array of proxy objects(not sure what the proxy says). I'm trying just to set the array of objects I get from api into mobx variable.
my store
class UserStore {
#persist #observable token = null
#observable tasks = []
#observable done = false
#persist #observable email = ''
constructor() {
}
#action
getTasks = async () => {
try {
let response = await Api.getTasks()
console.log('getTasks',response.tasks)
this.tasks = response.tasks
console.log('my new tasks',this.tasks)
} catch (e) {
console.log(e)
}
}
as you can see here in the first block('black') the data i get from api, then i set the respnse.tasks into this.tasks.
this.tasks = response.tasks
console.log('my new tasks',this.tasks)
You can convert proxy to JS:
import { toJS } from 'mobx'
// example
toJS(response)
It depends on how you want to observe the data.
"I'm trying just to set the array of objects I get from api into mobx variable"
is not really your end-goal.
If you want your observers to:
option a: react when the array reference change
= No care for values in the array.
Use #observable.ref tasks.
option b: react when the references of each value in the array change
= No care for the individual objects properties.
Use #observable.shallow tasks.
option c: react on the individual objects properties too
= Make everything observable, references and object properties
Use #observable tasks like you do.
Like indicated in the comments, mobx5 is using Proxy and some behaviour might differ compared to previous version.
More info: Mobx arrays, Mobx decorators, shallow observability
Note: you would need to give more details if this does not help you, like your react component code.
In my case, toJS was not working because I had a class with a variable with the type of another class. So, I needed to create new objects from the JSON:
parsedArray.map(
(a) =>
new MyObj1({
id: a.id,
myObj2: new MyObj2({
label: a.label,
}),
title: a.title,
})
);
If you run debugger, stop the debugger. It messes the mobx's flow.
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.
Forgive me for my ignorance but I've just started out with Aurelia/ES6 and a lot baffles me at the moment. I'm completely new to client side frameworks, so hopefully what I'm trying to achieve is possible within the framework.
So as the title indicates I'm fetching data within a class:
import {inject} from "aurelia-framework";
import {HttpClient} from "aurelia-http-client";
let baseUrl = "/FormDesigner/";
#inject(HttpClient)
export class FormData{
constructor(httpClient)
{
this.http = httpClient;
}
GetFormById(formId)
{
return this.http.get(`${baseUrl}/GetFormById/${formId}`)
.then(resp => resp.content);
};
}
Now I can see/receive the data which is great but after digging into the docs I cannot seem to figure out:
Load a separate related module/view by Id into the main view (app.html)
If no data, error and no Id passed then redirect to no-form view
Scenario:
User A navigates to "FormDesigner/#/form/3E7689F1-64F8-A5DA0099D992" at that point "A" lands on the form page, now if successful and data has been returned pass the formId into a different method elsewhere and then load in a module/view - Pages, possibly using <compose></compose>
This is probably really simple but the documentation (in my opinion) seems rather limited to someone that's new.
Really appreciate any guidance/high level concepts, as always, always much appreciated!
Regards,
Sounds like you might want to just partake in the routing lifecycle
If you are navigating to a module you can create an activate method on the view model which will be called when routing starts.
In this method you can return a promise (while you fetch data) and redirect if the fetch fails
In fact if the promise is rejected, the routing will be cancelled
If successful you can use whatever method you need to load in your module (assuming it can't just be part of the module that is being loaded since routing won't be cancelled)
Something like
activate(args, config) {
this.http.get(URL).then(resp => {
if (resp.isOk) {
// Do stuff
} else {
// Redirect
}
});
}