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.
Related
I have a Vue 3 app (without TypeScript), and I'm using a Vuex 4 store.
I have my store separated into modules that represent some common themes, for example I have a user module that contains the store, getters, actions etc that are appropriate for dealing with a user. But, I also have some common functionalities that is present in all of my modules, and it's an obvious place where I could simplify my modules and thin them out a bit.
Let's say for example that I have a generic set() mutator, like below:
const mutations = {
set(state, payload) {
state[payload.key] = payload.data;
}
}
This will simply take in a payload and populate whatever store object the payload 'key' field belongs to, and it works well when I want to simply set a single field in my store. Now, the issue is that this set() function is being duplicated in every single store module that I have since it's just a generic store mutation, and once the number of modules I reach increases a bit, you can imagine that it's quite wasteful and pointless to include this mutation every single time.
I'm not sure how to implement this, however.
My current main store file looks like this (simplified for the sake of the question):
// store/index.js
import { createStore } from "vuex";
import module1 from "./modules/module1";
import module2 from "./modules/module2";
export const store = createStore({
modules: { module1, module2 },
});
All of my modules are implemented in the same exact way:
// store/modules/module1.js
const state = { };
const mutations = { set(state, payload) };
const actions = { };
const getters = { };
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
And then somewhere in a component or wherever I'd call the specific module action/mutation/store I need with the below:
...mapMutations: ({ set: "module1/set" })
What would be the best way for me to bring that shared functionality out into a singular place, and how would I properly use it if I, for example, wanted to set and mutate the store in module1 and module2 at the same time properly? The part that I'm not sure about is how exactly I could call a generic mutation and have it target the desired module's state
Thanks to this answer linked by #Beyers, I've implemented the store in a similar fashion to this:
// store/sharedModuleMethods.js
export default class {
constructor() {
this.mutations = {
set(state, payload) {}
}
}
}
And then in the modules, I just instantiate the class and spread the methods where they need to be
// store/modules/module1.js
import SharedModuleMethods from "../sharedModuleMethods";
const methods = new SharedModuleMethods()
const mutations = {
...methods.mutations,
// Module specific mutations go here
};
And then in whatever component I need to set() something, I can just call the mutation like I would before
...mapMutations: ({ setModule1: "module1/set", setModule2: "module2/set" })
It's not quite as automated and streamlined as I'd personally prefer (define it once, have the modules magically have all the methods available to them via a flag or something), but alas life isn't perfect either.
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.
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 developing a large Angular2 application. The application contains a lot of components with similar behaviour. Following the DRY principle I have contained all common component functionality in a shared service.
However, for most components (e.g. foo.component.ts) I have a corresponsing "personal" service that fetches data for the specific component (e.g. foo.service.ts). To sum up all components make use of 2 services, they're own unique service and a shared one. The code for the "personal" service looks like this:
export class MasterDataService {
getData(param) {
return this._http.get("some/url/"+param+"/)
.map((res) => {
return res.json();
})
}
}
Currently, my components use the shared service in order to fetch data. The way I do this now is by calling a function in the shared service and sending the "personal" service as a paramter to that function:
public initializeController(dataService, component:any) {
this._activatedRoute.params.subscribe(
(param: any) => {
this._param = param;
dataService.getData(param).subscribe(
data => this._httpSuccess(data, component),
err => this._httpError(err)
);
}
);
}
In the same function I also send my component as a parameter:
// In component
this._controller.initializeController(this);
The reason for this is that I have a dataModel in my component that includes the data for my HTML. The generic shared service is in charge of calling a personal service, and updating the dataModel in my component.
setLocalData(data) {
console.log("Callback fired!!");
this.masterdataModel = data;
this.loading = false;
}
this.dataModel = {
data_1: "foo",
data_2: "bar",
};
I have found a solution that works, when setting my model data in my component through my shared service by sending "this" as a paramter from my component to my service function:
// In component
this._controller.initializeController(this);
// In service
public initializeController(component:any) {
component.dataModel = {/* some new data */}
}
I send the personal service and the component as a paramter because I cannot access the callback function or the personal service from the shared service in any other way.
Question 1: How do I structure this generic service or function that can call function from a "personal" service that each component has? Am I doing it in a correct manner or is there a much better way?
Question 2: How does my component call a service function or set a component callback or access a variable in my component from my service?
Question 3: The reason why I find this very dificult is because I am using "=>" to switch context of "this" in my shared service, which is why I ended up sending my component as a parameter. What solutions are there for this?
Any help is greatly appreciated!
You can provide the "personal service" at the component, then each component instance gets its own service instance (not sure if this is what you want)
#Component({
selector: 'my-component',
providers: [MyComponentPersonalService]
...
})
class MyComponent {
constructor(private personalService:MyComponentPersonalService) {}
Question 1: I suggest you make a method in the shared service that returns an Observable and the personal service subscribes to that observable and executes what's necessary when the shared service emits a value.
Question 2: Same as Question 1
Question 3: passing references around this way is quite weird and should be avoided at all
See also https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service for examples with observables.
I'm trying to develop a website where the nav-bar items depend on the role of the user who is logged in.
As Patrick Walter suggested on his blog, I was thinking to create a session.js file where I would store information about the current user: their username and role. I would then inject this file in nav-bar.js and create a filter for the routes, for which the user does not have access to. Everything worked fine until I hit the refresh button... In fact, it creates a new session object and I loose all the information store in the previous one.
I have seen in the docs the singleton method, but I'm not sure how to use it. If I insert it in my code such as below, I get the message: aurelia.use.singleton is not a function.
import config from './auth-config';
export function configure(aurelia) {
console.log('Hello from animation-main config');
aurelia.use
.singleton(Session)
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-animator-css')
.plugin('paulvanbladel/aurelia-auth', (baseConfig) => {
baseConfig.configure(config);
});
aurelia.start().then(a => a.setRoot());
}
export class Session {
username = '';
role = '';
reset() {
console.log('Resetting session');
this.username = '';
this.role = '';
};
}
My last idea would be to encrypt the role/username and use the browser's session to store the information. But I wanted to ask to more experienced developers their opinion about the topic.
Thanks for your help!
EDIT: Here is my code for session.js
export class Session {
username = '';
role = '';
reset() {
console.log('Resetting session');
this.username = '';
this.role = '';
};
}
And this is how I inject it:
import {Session} from './services/session';
#inject(Session)
export class RoleFilterValueConverter {
constructor(session) {
console.log('Hello from RoleFilter constructor', session)
this.session = session;
};
toView(routes, role) {
console.log('Hello from view', role, this.session)
if (this.session.role == 'Superuser')
return routes;
return routes.filter(r => {
var res = !r.config.role || (r.config.role == this.session.role);
return res
});
}
}
In the main entry point (let's assume it's index.html) you should have something like this:
<body aurelia-app="path/to/main">
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
System.import('aurelia-bootstrapper');
</script>
</body>
This imports the entire aurelia enviorment and so when it reaches the export function configure(aurelia) { ... } it should pass an instance of type Aurelia and bind it to the aurelia parameter and should resolve your aurelia.use.singleton is not a function. error. After that, in your session.js file when using #inject(Session) it should pass the same instance you declared at startup.
I also implemented a singleton session object to store user data and have chosen this method because it's more convenient to rely on dependency injection rather than always calling a method to get user data from a cookie.
Though Laurentiu's answer is not bad, there are better ways to handle this that do not add complexity to your app.
You do not need to need to specify this as a singleton. This particular function is more for an edge case where you would want to expose a particular item to the dependency injection container as a singleton before startup.
In fact, the Aurelia dependency injection framework treats all modules as singletons unless specified otherwise. Thus, the code should work as you have it written there, without the configure function.
I've written up an in-depth blog that you maay find helpful here: http://davismj.me/blog/aurelia-auth-pt2/