ES6 circular dependency. Implementing dependency hub - javascript

I found a lot of articles about circular dependencies and that it indicates the design/architecture flaws in the project. In most of the cases it can be fixed easily by slightly refactoring your classes.
In this example I'm trying to create a hub for dependencies. This is a library of classes that can be imported by different projects. The goal is to have getters in the main class that will return an instance of the dependency. This instance has to be a singleton and it's stored in the main class' dependencies that next time if someone calls a getter - it will return the same instance of that class.
Here is how code looks like:
// deps
import { DepA } from './dep-a';
import { DepB } from './dep-b';
const depsKey = '__MY_DEPS__';
class Deps {
dependencies = {};
get depA() {
return this.getDependency('depA', DepA);
}
get depB() {
return this.getDependency('depB', DepB);
}
bind(key, value) {
this.dependencies[key] = value;
}
getDependency(key, serviceClass) {
let service = this.dependencies[key];
if (!service && !!serviceClass) {
// if instance is not created yet, we instantiate the class and put instance in the dependencies
service = new serviceClass();
this.bind(key, service);
}
return service;
}
}
export const deps = (() => {
window[depsKey] = window[depsKey] || new Deps();
return window[depsKey];
})();
// dep-a
import { deps } from './deps';
export class DepA {
methodA() {
console.log(deps.depB);
}
}
// dep-b
import { deps } from './deps';
export class DepB {
methodB() {
console.log(deps.depA);
}
}
As you can see - this creates a circular dependency problem, since class Deps uses classes DepA and DepB in its getters to create an instance of those classes if it doesn't exist. And classes DepA and DepB use an instance of Deps to retrieve each other via its getters.
I hope this explanation is not very cumbersome.
Can anybody suggest the changes I need to make to get rid of the circular dependency here but to keep an idea of accessing singletons via Deps class (deps instance)?

My recommendation would be this:
a-singleton.js
import { DepA } from './dep-a';
let singleton;
export function getA() {
if (!singleton) {
singleton = new DepA();
}
return singleton;
}
b-singleton.js
import { DepB } from './dep-b';
let singleton;
export function getB() {
if (!singleton) {
singleton = new DepB();
}
return singleton;
}
then wherever you need these singletons, you can import the file and call the function to get the singleton.
Depending on what these classes do and how/when they reference eachother, you may also be able to directly do export default new DepA(), but depending on how they reference eachother, and how much they need to do at instantiation time, the lazy approach I've shown here can be better or necessary.

Related

Why would you need to null coalesce a static member initialization?

I'm looking through the Mixpanel for React Native example project and they're initializing a singleton like this:
import {Mixpanel} from 'mixpanel-react-native';
import {token as MixpanelToken} from './app.json';
export default class MixpanelManager {
static sharedInstance = MixpanelManager.sharedInstance || new MixpanelManager();
constructor() {
this.configMixpanel();
}
configMixpanel = async () => {
this.mixpanel = await Mixpanel.init(MixpanelToken);
}
}
I've never seen a singleton handled quite this way. What's the circumstance where MixpanelManager.sharedInstance will already be set when this class gets declared?

Vue JS - custom properties on vue component gives TS errors

I have created some Vue middleware and I am trying to add a custom property to one of my components in Vue like so:
middleware.js:
import { VueConstructor } from 'vue/types';
function eventPlugin(vue: VueConstructor): void {
const Socket = new someClass();
Object.defineProperties(vue.prototype, {
$socket: {
get: function get() {
return Socket;
},
},
});
vue.$socket = Socket;
}
myComponent.js
const MyComponent = Vue.extend({
name: 'MyComponent',
$socket: {
event(data: any) {
}
},
methods: {
MyMethod() {
}
}
})
app.js
import Vue from 'vue';
import eventPlugin from './middleware.js';
import MyComponent from './myComponent.js'
Vue.use(eventPlugin);
export default new Vue({
render: (h) => h(MyComponent),
}).$mount('#app');
The custom property I am trying to add here is obviously socket. The problem is when I add it I get typescript errors:
Object literal may only specify known properties, and 'socket' does
not exist in type 'ComponentOptions<Vue, DefaultData,
DefaultMethods, DefaultComputed, PropsDefinition<Record<string,
any>>, Record<...>>'.
As you can see in middleware.js I have tried defining the property there so I am not sure why I am receiving the error?
When adding instance properties or component options, you also need to augment the existing type declarations.
Based on Augmenting Types for Use with Plugins (Vue 2):
To type-hint the $socket instance property:
declare module 'vue/types/vue' {
interface VueConstructor {
$socket: string
}
}
export {}
To type-hint the $socket component option:
import Vue from 'vue'
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
$socket?: string
}
}
export {}
The type declarations above should go in a .d.ts file in your src directory. If using VS Code, any new .d.ts files might require restarting VS Code to load.

Hacking the import statement to extend an inherited class

In a library that I wish to extend without modifying its code, several classes inherit from the same imported one. That is in this BaseClass I would need to overwrite a specific method.
In the library (written in TypeScript) :
import { BaseClass } from './base_class';
export class ClassA extends BaseClass {}
import { BaseClass } from './base_class';
export class ClassB extends BaseClass {}
…
In the external extension I wish to write :
import { BaseClass } from 'library';
export class ExtendedBaseClass extends BaseClass {
oneMethod() {
const data = BaseClass.prototype.oneMethod.call(this);
// make additional things with data
return data;
}
}
Is there a way for this new ExtendedBaseClass to become the parent of all ClassXs ? At least in a new extended and re-exported version of them without the need to copy their internal code.
Is there a way for this new ExtendedBaseClass to become the parent of all ClassXs?
No.
An alternative might be to replace the one method directly on the base class:
import { BaseClass } from 'library';
const oneMethod = BaseClass.prototype.oneMethod;
Object.defineProperty(BaseClass.prototype, 'oneMethod', {
value() {
const data = oneMethod.call(this);
// make additional things with data
return data;
},
});
There's no way to do exactly what you're asking, but you could achieve the same result by extending each class individually.
ExtendedClassA extends ClassA {
oneMethod() {
// call a shared method if you need to reuse
}
}
// ExtendedClassB, etc

Setting up a utility class and using it within a vue component

A vue application I am working on currently has lots of code redundancies relating to date functions. In an effort to reduce these redundancies, I'd like to create a utility class as shown below, import it and set it to a Vue data property within the component, so I can call the date functions within it.
I am not certain on the best way to implement this. The current implementation results in an error saying TypeError: this.dates is undefined and my goal is not only to resolve this error but create/utilize the class in the Vue environment using best standards.
Importing utility class
import Dates from "./utility/Dates";
...
Component
const contactEditView = Vue.component('contact-edit-view', {
data() {
return {
contact: this.myContact
dates: Dates
}
},
...
Dates.js
export default {
dateSmall(date) {
return moment(date).format('L');
},
dateMedium(date) {
return moment(date).format('lll');
},
dateLarge(date) {
return moment(date).format('LLL');
}
};
View
Date of Birth: {{ dates.dateMedium(contact.dob) }}
My suggestion for this is to use a plugin option in Vue. About Vue plugin
So you will crate a new folder called services, add file yourCustomDateFormater.js:
const dateFormater = {}
dateFormater.install = function (Vue, options) {
Vue.prototype.$dateSmall = (value) => {
return moment(date).format('L')
}
Vue.prototype.$dateMedium = (value) => {
return moment(date).format('lll')
}
}
In main.js:
import YourCustomDateFormater from './services/yourCustomDateFormater'
Vue.use(YourCustomDateFormater)
And you can use it anywhere, like this:
this.$dateSmall(yourValue)
Or, if you want to use mixin. Read more about mixin
Create a new file dateFormater.js
export default {
methods: {
callMethod () {
console.log('my method')
}
}
}
Your component:
import dateFormater from '../services/dateFormater'
export default {
mixins: [dateFormater],
mounted () {
this.callMethod() // Call your function
}
}
Note: "Use global mixins sparsely and carefully, because it affects every single Vue instance created, including third party components. In most cases, you should only use it for custom option handling like demonstrated in the example above. It’s also a good idea to ship them as Plugins to avoid duplicate application." - Vue documentation
dateUtilsjs
import moment from 'moment-timezone'
function formatDateTime(date) {
return moment.utc(date).format("M/D/yyyy h:mm A")
}
export { formatDateTime }
Component JS
...
import { formatDateTime } from '../utils/dateUtils'
...
methods: {
formatDateTime,
}
Used within component
{{ formatDateTime(date) }}

Node.js v6.2.0 class extends is not a function error?

So I'm trying to extend a class in node js and the compiler keeps returning the following error:
TypeError: Class extends value #<Object> is not a function or null
I checked that I was exporting the class correctly and I am, any ideas? I'll post my code below:
/handler/venue.js:
var VenueViews = require('../views/venue'); // If I remove this the error will dissapear (as expected)
class Venue {
constructor(data) {
this.setDataHere = data;
}
main () {
var View = new VenueViews(); // This doesn't run
}
}
module.exports = Venue;
/views/venue.js:
var Venue = require('../handlers/venue');
console.log (Venue) // This returns {} ???
class VenueViews extends Venue {
constructor() {
super();
}
}
module.exports = VenueViews;
I know that node supports these es6 features, so I'm unsure why they aren't working?
Edit:
I'm not sure if this is suppose to happen but, when I log my Venue require it returns an empty object {}.
console.log (Venue) // This returns {} ???
So it turns out I had a circular reference in my code, where I was importing the class that was extending, into the class that itself was extending (tongue twister :P).
The obvious fix was to simply remove the extends reference and find another way of doing what I was trying to achieve. In my case it was passing the Venue class properties down into the VenueViews constructor.
E.g var x = VenueViews(this)
In my instance, it was the same issue as #James111 was experiencing (circular import) due to a factory pattern I was trying to set up in Typescript. My fix was to do move the code into files, similar to the following:
// ./src/interface.ts
import { ConcreteClass } from './concrete';
export interface BaseInterface {
someFunction(): any;
}
export class Factory {
static build(): BaseInterface {
return new ConcreteClass();
}
}
// ./src/base.ts
import { BaseInterface } from './interface';
class BaseClass implements BaseInterface {
someFunction(): any {
return true;
}
}
// ./src/concrete.ts
import { BaseClass } from './base';
export class ConcreteClass extends BaseClass {
someFunction(): any {
return false;
}
}
I had faced similar issue, after checking all the workaround finally issue got resolved by deleting the node_modules folder and run npm i.

Categories