Load different classes and execute similar function name - javascript

I have a problem that I can't resolve.
Let's say we have some classes in a directory named services.
Each of theses classes contain a contructor() and send() method.
We can have differents classes such as Discord, Slack, SMS, etc.
Their goal is just to sent notification through external service.
I think I have to use an interface or an abstract class which contain contructor() and send() method but how can I instanciate every class and call send() in an elegantly way ?
My project structure :
services/
-> discord.js
-> slack.js
-> [...]
index.js
Regards.

I think what you are looking for is a kind of manager where you have a single send() function that chooses a specific service based on a parameter. Something like this:
services/index.js
import SlackService from 'slack.js';
import DiscordService from 'discord.js';
export const TYPES = {
SLACK: 'slack',
DISCORD: 'discord',
};
export class ServiceManager {
services;
constructor() {
this.services = {
[TYPES.DISCORD]: new DiscordService(/* discordConfig */),
[TYPES.SLACK]: new SlackService(/* slackConfig */),
};
}
send(type, data) {
return this.services[type].send(data);
}
}
index.js
import ServiceManager from 'services/index.js';
const serviceManager = new ServiceManager();
serviceManager.send(ServiceManager.TYPES.SLACK, { message: 'Sent to Slack' });
serviceManager.send(ServiceManager.TYPES.DISCORD, { message: 'Sent to Discord' });
Dynamically loading services from files
You can use require-dir to import all files from a directory and then map over those to create each service. The individual service files have to be written in a defined syntax for the manager to use them. Something like this:
services/slack.js (as example for all service files):
export const name = 'slack';
export class Service {
constructor() {
// Set up connection to slack
}
send() {
// Send something to slack
}
}
services/index.js
const requireDir = require('require-dir');
export class ServiceManager {
services;
constructor() {
const serviceObjects = requireDir('.');
this.services = Object.values(serviceObjects).reduce(
(services, { name, Service }) => {
services[name] = new Service();
return services;
}
)
}
getRegisteredServices() {
return Object.keys(this.services);
}
send(name, data) {
return this.services[name].send(data);
}
sendAll(data) {
Object.values(this.services).each(service => service.send(data));
}
}
index.js (stays pretty much the same)
import ServiceManager from 'ServiceManager.js';
const serviceManager = new ServiceManager();
console.log('Registered services are: ', serviceManager.getRegisteredServices());
serviceManager.send('slack', { message: 'Sent to Slack' });
serviceManager.send('discord', { message: 'Sent to Discord' });

Related

how to write common function in lit element

I have some web components created using lit elements. And Those includes some api requests to send and fetch data from another site and I am hoping to send some data through api request header. So I am hoping to create one common function to contain those header details incase if i need to change them in the future, i won't need to edit those component one by one.
I need a solution like below :
common_function.js
function api_request(url) {
// content
}
my_component.js
import '/common_function.js';
...
constructor(){
api_request('http://apiRequestUrl');
}
Please let me know a way to achieve this using lit element.
Thanks in advance.
How to create a common function which you can import and reuse has nothing to do with lit.
So when you have a common function in a file named 'common.js':
export default txt => {
console.log('Hello from a common function!', txt);
}
You can use it in another javascript file, including lit components, like this:
import commonfunction from '/path/to/your/common.js';
commonfunction('Wow!');
What may be a problem (but which is not your question) is that your browser does not import lit with the lit import you may have specified... because it may be a npm package on your server. Even when you serve the node_modules folder and you specify the exact path and filename, your lit import may include other other imports that brake because of how they are specified as resolved by node.
Therefore you may have to use something like RollupJS which can distribute your app resources with proper imports. See https://rollupjs.org/guide/en/
Hope this helps.
Ok.. I found a solution. But I don't know whether this is the perfect answer for this.
We can use lit Reactive Controllers to do the job.
Here is the example How i did it.
common_function.js
import {initialState, Task} from '#lit-labs/task';
import * as SETTINGS from "../../bmw_settings";
export class ApiRequestController {
host;
url;
id;
task;
data = '';
_bmw_send_api_request() {
this.task = new Task(
this.host,
async ([data]) => {
const response = await fetch(
`${SETTINGS.BASE_URL + this.url}`,
{
headers: {
"Content-Type": "application/json",
'Access-Control-Request-Headers': 'Api-key, Content-Type',
'Api-key': '123'
}
}
);
const result = await response.json();
const error = result.error;
if (error !== undefined) {
throw new Error(error);
}
return result;
},
() => [this.data]
);
}
constructor(host, url, id) {
this.host = host;
this.url = url;
this.id = id;
this._bmw_send_api_request();
}
set data(value) {
this.data = value;
this.host.requestUpdate();
}
get data() {
return this.data;
}
render(renderFunctions) {
return this.task.render(renderFunctions);
}
}
my_component.js
import { LitElement, html} from 'lit';
import {ApiRequestController} from '../../common_functions';
class search_bar extends LitElement {
static properties = {
provider_type : String,
reasons : Array,
}
constructor() {
super();
this._getAllReasons();
}
async _getAllReasons(){
this.reasons = await new ApiRequestController(this, '/api/v1/get-reasons', 'search_bar');
}
render() {
return html `
${this.reasons.render({
complete: (data) => html `
<p>Reasons List</p>
<select>
<option>Select Reason</option>
${data.data.map((val) =>
html `<option value="${val.id}">${val.reason}</option>`
)}
</select>
`,
})}
`;
}
}
customElements.define('search-bar', search_bar)
use this documentation if you need more details.
Thank you

JavaScript ES6 instantiating with dependency injection classes which depend on each other

Still trying to get the hang of dependency injection, I've run into a problem. I have three classes: Main, Store, Model. Store does as the name suggests: stores data which is accessible elsewhere. Model retrieves data from the DB, in my case local/Chrome storage. I'm trying to write the classes in a decoupled way so I can test/modify it later. But of course, I keep getting Maximum call stack size exceeded error because I think Store and Model just keep going round and round...circular dependancy. What's a possible solution?
store.js
export class Store { //ok there's also a config class but that's not the problem
constructor(config, model) {
this._config = config;
this._model = model;
}
//...
}
model.js
export class Model {
constructor(config, store) {
this._config = config;
this._store = store;
}
//...
}
main.js
import { Store } from './store.js'
import { Config } from './config.js'
import { Model } from './model.js'
export class Main {
constructor(config, store, model) {
this._store = store;
this._model = model;
}
//...
}
const config = new Config();
const model = new Model(config, ???);
const store = new Store(config, ???);
const main = new Main(config, model, store);
Is there a fix, workaround, anything? Thanks in advance!

JS: blending two classes

Using Apollo server, have a pretty standard setup:
In an index.js file that manages my Apollo server state:
const { ApolloServer } = require('apollo-server-express');
const databaseAPI = require('../database/database.datasource');
...
module.exports = new ApolloServer({
...
dataSources: () => {
return {
databaseAPI: new databaseAPI()
};
},
...
});
And then my database.datasource.js file:
const { RESTDataSource } = require("apollo-datasource-rest");
// imports and config here
class databaseAPI extends RESTDataSource {
constructor() {
super();
}
// methods here like createUser({email, password, name})
}
module.exports = databaseAPI;
This database.datasource.js file is getting a bit lengthy, in part because it is managing the db layer for many types, so I was thinking about how to refactor it.
I want to break it apart by major types using a repository pattern, something like:
UserRepository.js (and similar abstract classes as reference and to enforce an internal API)
class UserRepository {
createUser({ email, password, name }) {
throw new Error("Not implemented");
}
...
}
export default UserRepository;
and then implementations of these in by-type directories:
/UserRepository/db.datasource.js
import UserRepository from "../UserRepository";
import utils from './utils';
class UserDatabaseAPI extends UserRepository {
constructor(db) {
super();
this.db = db;
}
createUser({ email, password, name }) {
// logic here
}
}
export default UserDatabaseAPI;
My question is how best to manage the multiple classes inside of my root database.datasource.js file with multiple inheritance sources.
I could see doing something like this:
const { RESTDataSource } = require("apollo-datasource-rest");
const UserDatabaseAPI = require("./UserRepository/db.datasource.js");
// imports and config here, including a db instance
const userDBAPI = new UserDatabaseAPI(db);
class databaseAPI extends RESTDataSource {
constructor() {
super();
}
createUser = userDBAPI.createUser;
...
}
Though I think that this might become kind of a headache. Is there another approach that might work better here, or is there at least a way to better map the methods in my userDBAPI instance to methods in my root database.datasource.js file?
Instead of having a single RESTDataSource for the endpoint you're wrapping, you can split it up into multiple RESTDataSource instances, one per resource. For example, https://swapi.co/api can be split into 6 difference RESTDataSource instances with the following baseURLs:
https://swapi.co/api/people
https://swapi.co/api/films
https://swapi.co/api/starships
https://swapi.co/api/vehicles
https://swapi.co/api/species
https://swapi.co/api/planets
And your config would look something like:
dataSources: () => ({
people: new PeopleAPI(),
films: new FilmsAPI(),
starships: new StarshipsAPI(),
vehicles: new VehiclesAPI(),
species: new SpeciesAPI(),
planets: new PlanetsAPI(),
})

How to cross import TypeScript classes?

Im trying to rewrite JavaScript code to TypeScript, but I'm running into some problems when trying to import a class into a different TypeScript file.
This used to be done with jQuery Namespaces, where it worked fine.
My situation is:
file Layouts:
import { Commands } from './funnel.commands'
var commands = new Commands(); /*<--- problematic line*/
export class Layouts {
loadHtmlSubLayout(done: Function, layoutname: string): void {
/*...*/
commands.CloseSubLayout();
/*...*/
};
closeSubLayout(layoutContent): void {
/*...*/
};
}
file Commands:
import { Layouts } from './funnel.layouts'
var layouts = new Layouts();
export class Commands {
GotoLayout(el, cmd: CommandObj): void {
/*...*/
layouts.loadSpecificLayout(layouts.onLayoutSwitched, layoutName);
};
CloseSubLayout(): void {
/*...*/
if ($subLayoutContent.length !== 0) {
layouts.closeSubLayout($subLayoutContent);
}
};
}
Whenever I try to run this, I get the error 'Commands is not a constructor'. How can I make sure this works without having to move the methods around?
One of these 2 module has to back-off and use async/lazy init the other in order to break the circular import chain.
In your example, I assume Commands is used ahead of Layouts. So you can do the following:
file funnel.commands
import { Layouts } from './funnel.layouts'
var layouts = new Layouts
export class Commands {
CloseSubLayout(): void {
layouts.closeSubLayout({});
};
}
file funnel.layouts
import { Commands } from './funnel.commands'
var commands: Commands
setTimeout(() => {
commands = new Commands()
})
export class Layouts {
loadHtmlSubLayout(): void {
commands.CloseSubLayout();
};
closeSubLayout(layoutContent): void {
console.log('You just called Layouts.closeSubLayout()')
};
}
other party:
import { Commands } from './funnel.commands'
var commands = new Commands()
commands.CloseSubLayout()
Above solution should work, but circular dependency is still an anti-pattern. Since you're rewriting a codebase, better refactor that part. I suggest use some sort of dependency injection pattern.
file dependencies
export const dependencies: any = {
_register(map: any) {
Object.assign(this, map)
}
}
file funnel.commands
import { dependencies as dep } from './dependencies'
export class Commands {
CloseSubLayout(): void {
dep.layouts.closeSubLayout({});
};
}
file index
import { Commands } from './funnel.commands'
import { Layouts } from './funnel.layouts'
import { dependencies } from './dependencies'
var commands = new Commands()
var layouts = new Layouts()
dependencies._register({ commands, layouts })

How to dynamically extend class with method in Typescript from remote js file?

I have TypeScript code with classes of components. And I want to use somehow remote js file to extend this classes remote. So I want when my app starts to get js file remote and use code this for extend of needed class.
How to extend class I know. For example:
Import { UsersBlocksMyOrders } from "../pages/users/blocks/myorders";
declare module "../pages/users/blocks/myorders" {
interface UsersBlocksMyOrders {
logit(): void;
}
}
UsersBlocksMyOrders.prototype.logit = function () { console.log(this); }
In component file the code is:
import { APP_CONFIG } from "../../../app/app.config";
#Component({
selector: 'menu-blocks-menupage',
templateUrl: APP_CONFIG.appDomain + '/mobilesiteapp/template/?path=pages/menu/blocks/menupage'
})
export class MenuBlocksMenuPage{
constructor(){
this.logit();
}
}
My problem is that I use the Webpack to compile code. Webpack create final file where name of function is different. That's why I can't access to class directly.
How to be in this situation?
Create service to get file and extend class. You need to have variable inside class which is would store object where have keys. Keys it is names of classes to extend. And values with imported classes. Inside init function we are loop extending with prototype method.
import { Http } from "#angular/http";
import { MenuBlocksMenuPage } from "../pages/menu/blocks/menupage";
export class ExtendService {
allModules = {
...
MenuBlocksMenuPage : MenuBlocksMenuPage,
...
};
constructor(public http: Http){ }
init()
{
//Load json map to extend class
this.http.get(APP_CONFIG.appDomain + '/modules/mobilesiteapp/view/js/extend.json').toPromise()
.then((res) => {
let json = res.json();
//Loop each class to extend
Object.keys(json).forEach((cl) => {
//Add new functions and methods
Object.keys(json[cl]).forEach((func) => {
this.allModules[cl].prototype[func] = eval(json[cl][func]);
});
});
}).catch((e) => {
console.error(e);
});
}
}
Request json file with functions to eval.
{
"MenuBlocksMenuPage": {
"logit": "(function (){console.log(this);})"
}
}

Categories