JS Design Pattern for setting a var set for functions - javascript

I am working on a react project but I think this question relates to all JS
I have a Token file that contains the following functions:
export default {
set(token) {
Cookies.set(key, token);
return localStorage.setItem(key, token);
},
clear() {
Cookies.remove(key);
return localStorage.removeItem(key);
},
get() {
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
},
Now I want to add a set of what are essentially environment variables for the domain of these 3 functions. In my case its based on window.location.hostname but could really be anything.
In this instance lets say we want key to be dev, uat or prod based on window.location.hostname
getKey = host => {
if(host === 'a')
return 'dev'
elseIf (host === 'b')
return 'uat'
else
return 'prod'
}
I think the above is fairly standard to return the key you want. but what if your key has 6 vars, or 8, or 20. How could you set all the vars so that when you call set(), clear() and get() that they have access to them?
Basically I want to wrap the export in a function that sets some vars?
To illustrate this a bit more
class session extends Repo {
static state = {
current: false,
};
current(bool) {
this.state.current = bool;
return this;
}
query(values) {
<Sequelize Query>
});
}
export session = new Session();
using this I can call current(true).session() and sessions state would be set to true. I want to apply a similar pattern to the Token file but I don't want to change all my calls from Token.set(token) to Token.env(hostname).set(token)

This acomplished what I wanted, I had to call the function from within the others as window is not available on load. It essentially illustrates the pattern I was looking for. Thanks for Jim Jeffries for pointing me towards the answer.
class Token {
constructor(props) {
this.state = {
testKey: null,
};
}
setCred = host => {
if (host === 'uat') {
this.state.testKey = 'uat';
} else if (host === 'prod') {
this.state.testKey = 'prod';
} else {
this.state.testKey = 'dev';
}
};
set(token) {
this.setCred(window.location.hostname);
Cookies.set(testKey, token);
return localStorage.setItem(testKey, token);
}
clear() {
this.setCred(window.location.hostname);
Cookies.remove(testKey);
return localStorage.removeItem(testKey);
}
get() {
this.setCred(window.location.hostname);
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
}
}
export default new Token();
If anyone else has another idea please share.

Related

Passing a string to set the default language does not work

I am currently working on an app that does translation work. The English language should be used as the default setting. The actual translation is done by ngx-translate. It is temporarily stored in the variable translateService, which is declared in the constructor.
the constructor:
constructor(private translateService: TranslateService, private platform: Platform, private globalization: Globalization, private storage: Storage)
The variable defaultLang is provided with the string 'en', which stands for English. The translateService now receives the variable defaultLang. But still, English is not taken as the default language. What is the reason for this?
async initialize() {
if (this.isInitialized) { return Promise.resolve(); } if (this.initializeListeners.length > 0) { // a parallel call has already been started
return new Promise(success => { this.initializeListeners.push(success); });
} return new Promise(async success => {
let defaultLang = 'en'; this.translateService.setDefaultLang(defaultLang); this.initializeListeners.push(success); await this.platform.ready(); let lang = await this.storage.get(LanguageService.STORAGE_KEY); if (!lang && checkAvailability(Globalization.getPluginRef(), null, Globalization.getPluginName()) === true) {
// no previous language found -> get device language
try {
let preferredLanguage = await this.globalization.getPreferredLanguage(); if (preferredLanguage && preferredLanguage.value) {
lang = preferredLanguage.value; if (lang.indexOf('-') > -1) { lang = lang.split('-')[0]; } if (lang.indexOf('_') > -1) { lang = lang.split('_')[0]; } lang = lang.toLowerCase(); if (this.availableLanguages.indexOf(lang) == -1) {
// detected language is not available
lang = null;
}
}
} catch (e) { }
} if (!lang) { lang = defaultLang; } this.textDirection = this.rtlLanguages.indexOf(lang) >= 0 ? 'rtl' : 'ltr';
this.platform.setDir(this.rtlLanguages.indexOf(lang) >= 0 ? 'rtl' : 'ltr', true); await this.translateService.use(lang); setTimeout(() => {
// use timeout to let language switch propagate
this.isInitialized = true; this.initializeListeners.map(success => { success(); }); this.initializeListeners = null;
}, 100);
});
}
I assumed that the declaration of a variable passing the corresponding string would be sufficient.

How to generate getters and setters in Javascript?

I have the following code, which is very repetitious:
const flags = {
get logged() {
return localStorage.getItem("logged") === "true";
},
set logged(val: boolean) {
if (val) {
localStorage.setItem("logged", "true");
} else {
localStorage.removeItem("logged");
}
},
get notificationsMuted() {
return localStorage.getItem("notifications-muted") === "true";
},
set notificationsMuted(val: boolean) {
if (val) {
localStorage.setItem("notifications-muted", "true");
} else {
localStorage.removeItem("notifications-muted");
}
}
}
As you can see, the get and set for each flag type is identical, save for the property names. I would like to do something like this instead:
function getter(prop: string) {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged: getter("logged")
set logged: setter("logged")
get notificationsMuted: getter("notifications-muted")
set notificationsMuted: setter("notifications-muted")
}
But I'm not sure if Javascript / Typescript has support for this sort of thing. Is such a thing possible, and if so, how? If not, is there any other way I can cut down on the repetition here?
You can use a proxy with get and set traps, use TS types to allow only props you wish to handle (TS playground)):
interface Flags {
logged: boolean,
'notifications-muted': boolean;
}
type Prop = keyof Flags;
const handlers = {
get(_: Flags, prop: Prop) {
return localStorage.getItem(prop) === "true";
},
set(_: Flags, prop: Prop, val: any) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
return true;
}
};
const flags = new Proxy<Flags>({} as Flags, handlers);
All you really need is to use Object.defineProperty with an object with a get and set properties. Or, with multiple properties, use Object.defineProperties to define them all at once.
One approach which will help with code organization is to not use lots of local storage keys, but instead use a single object that gets stored.
const props = ['logged', 'notificationsMuted'] as const;
const defaultStorage = Object.fromEntries(props.map(prop => [prop, false]));
const getStorage = () => JSON.parse(localStorage.getItem('settings') || JSON.stringify(defaultStorage));
const flags = Object.defineProperties(
{},
Object.fromEntries(
props.map(
prop => [
prop,
{
get: () => getStorage()[prop],
set: (newVal: boolean) => {
const store = getStorage();
store.prop = newVal;
localStorage.setItem('settings', JSON.stringify(store));
}
}
]
)
)
) as Record<(typeof props)[number], boolean>;
This is the current "best" solution I can come up with. Open to anyone who can provide an improvement over this:
function getter(prop: string): boolean {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean): void {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged() { return getter("logged") },
set logged(val: boolean) { setter("logged", val) },
get notificationsMuted() { return getter("notifications-muted"); },
set notificationsMuted(val: boolean) { setter("notifications-muted", val); }
}

How to get data value in regular js file from vue component?

I have component MyComponent.vue where I have data value that constantly changes. I want to pass this value to javascript file(js file should know about changes of value everytime)
Why do I do that? Because my regular js file is a service layer for axios methods. I can import this file in many other components. The file contains axios methods and urls are dynamic.
I want those urls depend on data variable. This data variable comes from MyComponent.js
So the main goal is to make dynamic urls of axios that depend on data variable
I tried some code but it doesn't work, because js file(CategoryService.js) know nothing about this.categoryNumber.
MyComponent.vue:
<script>
export default {
data() {
return {
categoryNumber: 1
}
}
}
</script>
CategoryService.js
import http from "../../../http-common";
let category = "category1";
if (this.categoryNumber === 1) {
category = "category1";
} if (this.categoryNumber === 2) {
category = "category2";
}
class CategoryService {
get(id) {
return http.get(`/${category}/${id}`);
}
update(id, data) {
return http.put(`/${category}/${id}`, data);
}
create(data) {
return http.post(`/${category}`, data);
}
delete(id) {
return http.delete(`/${category}/${id}`);
}
getAll() {
return http.get(`/${category}/all`);
}
}
export default new CategoryService();
So with a bit of refactoring, you could easily get this working.
First of all, I would put the if/else logic of your class into it.
For convenience and scalability, I would use a Vuex store that will keep track of your categoryNumber and share it accross all your components.
Then I would bind my service to my Vue instance so I can easily access it in all my components as well as the store and I would pass the latter to my class as a parameter.
For the last part, I don't know the logic in the http-common file so the code I will show you is a bit nasty. But depending on wether or not you bound 'http' to axios, you could make use of axios interceptors to call the getCategoryNumber() method in every request.
Here's an idea of the implementation I would go for:
const CategoryService = class CategoryService {
constructor(store) {
this._store = store;
this.category = "category1";
}
getCategoryNumber() {
if (this._store.state.categoryNumber === 1) {
this.category = "category1";
}
if (this._store.state.categoryNumber === 2) {
this.category = "category2";
}
console.log(this.category); // for demo puprose
}
get(id) {
this.getCategoryNumber(); // We could use axios request interceptor instead of calling that in every route, but that works !
return http.get(`/${this.category}/${id}`);
}
update(id, data) {
this.getCategoryNumber();
return http.put(`/${this.category}/${id}`, data);
}
create(data) {
this.getCategoryNumber();
return http.post(`/${this.category}`, data);
}
delete(id) {
this.getCategoryNumber();
return http.delete(`/${this.category}/${id}`);
}
getAll() {
this.getCategoryNumber();
return http.get(`/${this.category}/all`);
}
}
const store = new Vuex.Store({
state: {
categoryNumber: 1
},
mutations: {
setCategoryNumber(state, payload) {
state.categoryNumber = payload;
}
}
});
// Bind your service to the Vue prototype so you can easily use it in any component with 'this.$service'
// pass it the store instance as parameter
Vue.prototype.$service = new CategoryService(store);
new Vue({
el: "#app",
store, // dont forget to bind your store to your Vue instance
methods: {
updateCategoryNumber() {
// Put here any logic to update the number
this.categoryNumber = this.categoryNumber === 1 ? 2 : 1;
this.checkServiceCategoryValue();
},
checkServiceCategoryValue() {
// for demonstration purpose
this.$service.getCategoryNumber();
}
},
computed: {
// Look for the store value and update it
categoryNumber: {
get() {
return this.$store.state.categoryNumber;
},
set(value) {
this.$store.commit("setCategoryNumber", value);
}
}
}
});
<div id="app">
<h2>number: {{ categoryNumber }}</h2>
<button type="button" #click="updateCategoryNumber()">
updateCategoryNumber
</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#2.0.0"></script>
Thanks to #Solar
I just added one more parameter for all urls and put the number of category to it
CategoryService.js:
class CategoryOneService {
get(id, category) {
return http.get(`/${category}/${id}`);
}
getAll(category) {
return http.get(`/${category}/all`);
}
}
functions.js:
let catNum = "";
function getQuestion() {
if (this.categoryNumber === 1) {
catNum = "category1";
}
if (this.categoryNumber === 2) {
catNum = "category2";
}
let questionId = this.questionNumber;
CategoryOneService.get(questionId, catNum)
.then(response => {
this.question = response.data.question;
this.answer = response.data.answer;
})
.catch(error => {
console.log(error);
});
}

Angular Unit Test: How to mock properties in a method?

Here is the service that I am trying to test:
#Injectable()
export class BomRevisiosnsService {
constructor(
private baseService: BaseService,
private appConstants: AppConstants,
private dmConstants: DMConstants
) { }
public getRevisionsData(): any {
var itemId = this.appConstants.userPreferences.modelData['basicDetails']['itemId'];
let url = this.dmConstants.URLs.GETBOMREVISIONS + itemId + "/GetRevisionsAsync";
let headers = {
"Content-Type": "application/json",
UserExecutionContext: JSON.stringify(this.appConstants.userPreferences.UserBasicDetails),
}
if (itemId != null || itemId != undefined) {
return this.baseService.getData(url, headers).map(response => {
return response;
});
}
}
}
spec file
describe('bom-revisions.service ',()=>{
let bomRevisiosnsService:BomRevisiosnsService;
let baseService: BaseService;
let appConstants: AppConstants;
let dmConstants: DMConstants;
beforeEach(()=>{
baseService=new BaseService(null,null);
appConstants=null;
dmConstants=null;
bomRevisiosnsService=new BomRevisiosnsService(baseService,appConstants,dmConstants);
});
it('getRevisionsData() call base service getData()',()=>{
let spy = spyOn(baseService, 'getData').and.returnValue(Observable.of())
bomRevisiosnsService.getRevisionsData();
expect(baseService.getData).toHaveBeenCalled();
});
})
Error: TypeError: Cannot read property 'userPreferences' of null
I believe I need to provide some mock value for this.appConstants.userPreferences.modelData['basicDetails']['itemId'];
and this.dmConstants.URLs.GETBOMREVISIONS + itemId + "/GetRevisionsAsync";
Yes, indeed, you need to provide a valid value for appConstants and dmConstants because the call to bomRevisiosnsService.getRevisionsData() uses that information internally.
So, instead of assigning null to appConstants and dmConstants, you could create an objects with some valid data, like this:
appConstants = {
userPreferences: {
modelData: {
basicDetails: {
itemId: 3 // some other valid value here is fine
}
},
UserBasicDetails: {
// some valid values here, maybe
}
}
};
dmConstants = {
URLs: {
GETBOMREVISIONS: 'revisions' // or just some valid value according to the use case
}
};
And the same goes to baseService.
In general, you need to create valid stub, mock, etc for all object, services, etc that are used internally by the service you're testing.

Returning inner $http promise

I have two services:
ProductService
CartService
And a Controller:
ShoppingController
ShoppingController needs to download the cart from the server. In order for CartService to do this, ProductService must first download the products.
ProductService.js
ProductService.DownloadProducts = function(){
if(alreadyHaveDownloadedProducts){
return {
success: function (fn) {
fn(products);
}
};
}else{
if(getProductsPromise== null){
getProductsPromise= $http.post("Api/GetProducts")
}
return getProductsPromise;
}
CartService.js
CartService.DownloadCart = function(){
ProductService.DownloadProducts().success(function(){
if(alreadyHaveDownloadedCart){
//Dont need to go to server
return {
success: function (fn) {
fn(cart);
}
};
}else{
if(getCartPromise == null){
getCartPromise = $http.post("Api/GetCart")
}
return getCartPromise; //<= The promise I actually want to return
}
})
}
ProductService.DownloadProducts
ShoppingController.js
CartService.DownloadCart().success(function(){DisplayCart()});
This approach works nicely so far, because if the ProductService has already been called on a different page, I don't need to go back to the server. Same for cart service.The issue is I currently can't return the getCartPromise as it hasn't been created until the async GetProducts has returned
Is it possible to structure this so I can return the inner promise to ShoppingController while keeping the nice .success() syntax?
My way is similar to yours, but instead of saving some alreadyHaveDownloadedCart (boolean flag), i'm caching the promise it self, and then returns it.
I'm caching the promise for the data on the service class, and then returning it if it is exists otherwise initialize a server call.
Something like that:
class ProductService {
constructor() {
this.productsPromise = null;
}
DownloadProducts() {
if(!this.productsPromise) {
this.productsPromise = $http.post('Api/GetProducts');
this.productsPromise.then(products => this.products = products);
}
return this.productsPromise.then(() => this.products);
}
}
class CartService {
constructor(ProductService) {
this.ProductService = ProductService;
this.cartPromise = null;
}
DownloadCart() {
return this.ProductService.DownloadProducts().success(() => {
if (!this.cartPromise) {
this.cartPromise = $http.post('Api/GetCart');
this.cartPromise.then((cart) => {
this.cart = cart;
});
}
return this.cartPromise.then(() => this.cart);
});
}
}

Categories

Resources