I have a multiple Svelte stores that looks like this:
import { readable } from "svelte/store";
export const NatureStore = readable([
{
name: "nature",
description:
"Nature is what makes us all so we need to beautify it. This represent what we all need to survive in a civilization.",
images: {
src: [
"...",
],
description: [
"...",
],
},
},
]);
All my stores have the same organization.
Now my main problem is that I need to subscribe to a specific store depending on a variable when the component is loaded.
Like for example if my variable is var = 'nature' I want to subscribe to nature. I could do it with ifs and elses, but that won't be efficient at all. So I wonder if there's a way to do it in a better way.
For now I only subscribe to one store and it looks like that:
<script>
import { NatureStore as nature } from "../../stores/nature";
import { BlooperStore } from "../../stores/bloopers";
import { NightStore } from "../../stores/night";
import { PortraitStore } from "../../stores/portrait";
import { PurpleStore } from "../../stores/purple";
import { UrbanStore } from "../../stores/urban";
import { UrbexStore } from "../../stores/urbex";
import { onDestroy } from "svelte";
//parameter passed with the router
export let params;
let category = params.category;
let pictures = [];
const unsubscribe = nature.subscribe((data) => {
pictures = data;
});
onDestroy(() => unsubscribe());
</script>
But i want something like this :
<script>
import { NatureStore as nature } from "../../stores/nature";
import { BlooperStore } from "../../stores/bloopers";
import { NightStore } from "../../stores/night";
import { PortraitStore } from "../../stores/portrait";
import { PurpleStore } from "../../stores/purple";
import { UrbanStore } from "../../stores/urban";
import { UrbexStore } from "../../stores/urbex";
import { onDestroy } from "svelte";
//parameter passed with the router
export let params;
let category = params.category;
let pictures = [];
//here setting the subscribe to the category variable
const unsubscribe = category.subscribe((data) => {
pictures = data;
});
onDestroy(() => unsubscribe());
</script>
If my explanations are not clear leave a comment I'll answer as quick as possible.
Thanks for your help.
You can create a mapping from category to store and just use that to find the correct one. The mapping could be extracted from the stores themselves or has to be created manually. As the store contents are an array, not an object, the former approach might not work.
One should not manually subscribe in Svelte files, just use $-syntax and reactive statements. That way you can't forget to unsubscribe and that happens automatically.
E.g.
<script>
import { NatureStore } from "../../stores/nature";
import { BlooperStore } from "../../stores/bloopers";
import { NightStore } from "../../stores/night";
import { PortraitStore } from "../../stores/portrait";
import { PurpleStore } from "../../stores/purple";
import { UrbanStore } from "../../stores/urban";
import { UrbexStore } from "../../stores/urbex";
export let params;
$: category = params.category;
const mapping = {
nature: NatureStore,
bloopers: BlooperStore,
night: NightStore,
portrait: PortraitStore,
purple: PurpleStore,
urban: UrbanStore,
urbex: UrbexStore,
}
$: store = mapping[category];
$: pictures = $store;
</script>
Related
In vue3, I try to add attributes like $attrsWithoutIdClassStyle and $attrsIdClassStyleOnly to the globalProperties which the values are based on the $attrs of current vue-component
It can be achieved by the global mixins
import _ from 'lodash';
import { createApp } from 'vue';
import App from '~/App.vue';
createApp(App)
mixin({
computed: {
$attrsWithoutIdClassStyle() {
return _.omit(this.$attrs, ['id', 'class', 'style']);
},
$attrsIdClassStyleOnly() {
return _.pick(this.$attrs, ['id', 'class', 'style']);
},
}
})
But since the mixin is not recommended in vue3 any more, also the code is ugly.
So, how can I add the custom attributes to the global properties via app-config-globalproperties which is a dynamic value based on the current vm?
As the documentation suggests, the proper Vue 3 replacement is to use a composable function and import where needed.
composables/myAttrs.js
import { useAttrs } from 'vue';
import _ from 'lodash';
export function useMyAttrs() {
const attrs = useAttrs();
const attrsWithoutIdClassStyle = _.omit(attrs, ['id', 'class', 'style']);
const attrsIdClassStyleOnly = _.pick(attrs, ['id', 'class', 'style']);
return {
attrsWithoutIdClassStyle,
attrsIdClassStyleOnly
};
}
in your components,
import { useMyAttrs } from '#/composables/myAttrs';
const { attrsWithoutIdClassStyle, attrsIdClassStyleOnly } = useMyAttrs();
In a Vue project that's underway, the files which are responsible for communicating with the API have been grouped into a "services" section. While the code shown below works fine as written, it seems like simplifying it would be beneficial due to these factors:
there will be several more API specific files needed to support the project
the code in the 2 example API specific files shown below is essentially the same except for the value in the resource const
src/services/service.js
import axios from 'axios';
export default axios.create({
baseURL: 'advancedplanningextension/api/'
})
API specific files -
src/services/official-scenarios-service.js
import service from './service';
const resource = 'scenariolog';
export default {
get() {
return service.get(`${resource}`);
}
}
src/services/parameters-service.js
import service from './service';
const resource = 'parameter';
export default {
get() {
return service.get(`${resource}`);
}
}
file to choose the right API specific file -
src/services/service-factory.js
import OfficialScenariosService from './official-scenarios-service';
import ParametersService from './parameters-service';
const services = {
officialScenarios: OfficialScenariosService,
parameters: ParametersService
// add more here
}
export const ServiceFactory = {
get: name => services[name]
}
plugin file -
src/plugins/service.js
import { ServiceFactory } from '../services/service-factory';
export default {
install: function(Vue) {
Object.defineProperty(Vue.prototype, '$service', { value: ServiceFactory });
}
}
pertinent code in src/main.js that shows wiring of plugin file -
import ServicePlugin from './plugins/service';
Vue.use(ServicePlugin);
Example usage in src/views/official-scenarios.vue -
<template>
{{ officialScenarios }}
</template>
<script>
export default {
data() {
return {
officialScenarios: []
}
},
methods: {
async getOfficialScenarios() {
const { data } = await this.$service.get('officialScenarios').get();
this.officialScenarios = data;
}
},
mounted: function () {
this.getOfficialScenarios();
}
}
</script>
I tried creating a src/services/generic-service.js file (to replace the 2 API specific files), with the intent of calling that from src/services/service-factory.js, but quickly realized I didn't know how to populate the resource when it was called in that manner.
src/services/generic-service.js -
import service from './service';
let resource = ''; // don't know how to populate this from caller service-factory.js to access the correct API
export default {
get() {
return service.get(`${resource}`);
}
}
Is there a way to set the resource variable when service-factory.js calls the generic-service.js file? Or perhaps there is a way to incorporate the logic from generic-service.js directly into service-factory.js...? Or maybe there is a simpler approach than these options...?
OUTCOME
Many thanks to #Estus Flask for the answer and follow up information. For now, I decided to put all the services logic into src/services/index.js. If the project grows beyond needing more than createService I'll deal with refactoring at that point. Below is the code that seems to be working -
src/services/index.js -
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'advancedplanningextension/api/'
});
const createService = resource => ({
get() {
return axiosInstance.get(`${resource}`);
}
// add more here
});
const resources = {
officialScenarios: 'scenariolog',
parameters: 'parameter'
// add more here
}
export const service = {
get: name => createService(resources[name])
}
The values in resources are the specific API endpoints. So when the code calls the service for officialScenarios for example, the URL for that API endpoint will be 'advancedplanningextension/api/scenariolog'.
src/plugins/service.js -
import { service } from '../services/index';
export default {
install: function(Vue) {
Object.defineProperty(Vue.prototype, '$service', { value: service });
}
}
Example usage in src/views/official-scenarios.vue remains the same as shown in the original question.
This can be solved with factory function:
base-service.js
import axios from 'axios';
export const axiosInstance = axios.create({
baseURL: 'advancedplanningextension/api/'
});
export const createService = resource => ({
get() {
return axiosInstance.get(`${resource}`);
},
// etc.
});
foo-service.js
import { createService } from './base-service';
export default createService('foo');
service-factory name is misleading because it isn't really a factory, just a wrapper module. The same thing can be rewritten more efficiently as barrel module (which is commonly index module):
services/index.js
export { default as foo } from './foo-service';
export { default as bar } from './foo-service';
Then it gets all benefits of ES modules such as tree-shaking and can be used as:
import * as services from './services';
services.foo.get(...);
I have some code on react that should re-render the main page when I click. but it does not work. Can someone suggest an idea?
/store/index.js
import jsonObjects from './jsonObjects';
class RootStore {
constructor() {
this.jsonObjects = new jsonObjects(this);
}
}
export default RootStore;
/store/jsonObjects.js
import sciences from '../data/sciences.json'
class JsonObjects {
jsonObjectsList = observable(JSON.parse(sciences))
get jsonObjects() {
return this.jsonObjectsList.filter(jsonObjects => jsonObjects.deleted === false);
}
handleClick() {}
}
decorate(JsonObjects, {
handleClick: action,
jsonObjects: computed
})
export default JsonObjects;
I want to empty the database before my acceptance test cases.
In my aircraft.controller.acceptance.ts I have
import { givenEmptyDatabase } from './helpers/database.helpers';
...
before(givenEmptyDatabase);
...
describe( ... ) { ... }
in database.helpers.ts I try to act according to the LoopBack 4 documentation
import {AircraftRepository} from '../../src/repositories';
import {testdb} from '../fixtures/datasources/testdb.datasource';
export async function givenEmptyDatabase() {
await new AircraftRepository(testdb).deleteAll();
}
but it's never described what the testdb.datasource.ts should look like. I have tried to make it look similar to my regular db.datasource.ts, but I'm not sure what to export..
import {inject} from '#loopback/core';
import {juggler, AnyObject} from '#loopback/repository';
const config = require('./db.datasource.json');
export class DbDataSource extends juggler.DataSource {
static dataSourceName = 'db';
constructor(
#inject('datasources.config.db', {optional: true})
dsConfig: AnyObject = config
) {
super(dsConfig);
}
}
Got an answer from the #loopback/repository. testdb.datasource.ts should look like this:
import { juggler } from '#loopback/repository';
export const testdb: juggler.DataSource = new juggler.DataSource({
name: 'db',
connector: 'memory'
});
I've the following code:
app.html:
<ion-menu [content]="content">
<ion-content [ngStyle]="{'background': 'linear-gradient(#'+menuColor1+', #'+menuColor2+')'}">
...
</ion-content>
</ion-menu>
<ion-nav #content [root]="rootPage" swipeBackEnabled="false"></ion-nav>
app.components.ts:
import { Component } from '#angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { TabsPage } from '../pages/tabs/tabs';
import { GlobalsProvider } from '../providers/globals/globals';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage: any = TabsPage;
menuColor1: string = GlobalsProvider.MENU_COLOR_1;
menuColor2: string = GlobalsProvider.MENU_COLOR_2;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
GlobalsProvider.MENU_COLOR_1 = "f00";
GlobalsProvider.MENU_COLOR_2 = "ff0";
console.log(GlobalsProvider.MENU_COLOR_1); // f00
console.log(this.menuColor1); // 000 (expected: f00)
console.log(GlobalsProvider.MENU_COLOR_2); // ff0
console.log(this.menuColor2); // 000 (expected: ff0)
});
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}
globals.ts:
export const GlobalsProvider = {
MENU_COLOR_1: "000",
MENU_COLOR_2: "000",
}
I want to do a two way data binding between the app.component.ts and globals.ts, for each time that vars MENU_COLOR_1 and MENU_COLOR_2 are changed, update too in menuColor1 and menuColor2 values (I will update MENU_COLOR_1 and MENU_COLOR_2 from another componenets too in the future and I want that menuColor1 and menuColor2 receive these changes).
It is possible?
Objects are mutable and are passed by reference. Primitive types, like string are not, therefore they are not changing as you hope. So what you should do, is to work with objects:
menuColor1 = GlobalsProvider;
menuColor2 = GlobalsProvider;
ngOnInit() {
GlobalsProvider.MENU_COLOR_1 = "f00";
GlobalsProvider.MENU_COLOR_2 = "ff0";
console.log(GlobalsProvider.MENU_COLOR_1); // f00
console.log(this.menuColor1.MENU_COLOR_1);
console.log(GlobalsProvider.MENU_COLOR_2); // ff0
console.log(this.menuColor2.MENU_COLOR_2);
}
But be very careful with this, since as mentioned, objects are passed by reference, so sometimes you might not want this behavior.