JavaScript ES6 circular require references - javascript

I've moved a bunch of data onto the UI represented by classes. The different classes all map to each other using FKs. I'm trying to keep references to those data classes to only 1, to avoid the confusing pointers, so I keep the FKs as Ints on the related classes, then look them up when I need to. Originally I had no Cache class, but I tried to create a pipe for the data between the UI and data, and funneled all the cached-data requests through its own class (which I thought would help with the circular references too) but no dice unfortunately.
I sandboxed the below example to try to simplify my issue. I used static methods to simplify the example. Also the sandbox has more-easily hierarchical classes, unlike the app. The example's probably overly-complicated but I'm trying my best to show why I (think I) need the circular references. This might be an issue with webpack, or maybe a philosophical one where I shouldn't have circular references, any feedback would be incredible! (I think the same scenario would break node.js as well)
Cache.js
const Person = require("./Person.js")
const Group = require("./Group.js")
class Cache {
static set() {
Person.set([
{ name: "Sean", groupID: 1 },
{ name: "Greg", groupID: 1 }
])
Group.set([ {id: 1, name: "A"} ])
}
static getGroupByID(id) {
return Group.getByID(id)
}
static getPeopleInGroup(id) {
return Person.getInGroup(id)
}
static getFirstPerson() {
return Person.getFirstPerson()
}
}
module.exports = Cache
/* use closures?
module.exports = {
set: () => Cache.set()
getGroupByID: (id) => Cache.getGroupByID(id),
getPeopleInGroup: (id) => Cache.getPeopleInGroup(id),
getFirstPerson: () => Cache.getFirstPerson()
}
*/
Person.js
const Cache = require("./Cache.js")
const list = []
class Person {
static set(people) {
people.forEach( p => {
list.push( new Person(p) )
})
}
static getInGroup(id) {
const ar = []
list.forEach( p => {
if (p.data.GroupID == id)
ar.push(p)
})
return ar
}
static getFirstPerson() {
return list[0]
}
constructor(data) {
this.data = data
}
sayHello() {
const group = Cache.getGroupByID(this.data.groupID)
const groupName = group.data.name
console.log(`${this.data.name} from ${groupName}`)
}
}
module.exports = Person
Group.js
const Cache = require("./Cache.js")
const hash = {}
class Group {
static set(groups) {
groups.forEach( g => {
hash[g.ID] = new Group(g)
})
}
static getGroupByID(id) {
return hash[id]
}
constructor(data) {
this.data = data
}
sayPeople() {
const ppl = Cache.getPeopleInGroup(this.data.ID)
console.log(`${this.data.name} has ${ppl.join(", ")}`)
}
}
module.exports = Group
app.js
const Cache = require("./Cache.js")
Cache.set()
let person = Cache.getFirstPerson()
person.sayHello()
let group = Cache.getGroupByID(person.data.groupID)
group.sayPeople()
In this example, the first bad reference is:
Class Person { ...
sayHello() {
const group = Cache.getGroupByID(this.data.groupID)
(Cache is an empty object)

So, cache.js requires person.js and then person.js requires cache.js. That is a circular require that gives you the problem you see.
The design issue here is that person.js checks cache.js for data (that seems reasonable), but you've made methods in cache.js that require knowledge of specific data types from the cache. That creates your circular dependency.
The usual generic answer to these circular dependencies is to figure out what code is needed by both modules and break that out into it's own module where that module does not depend upon anything.
It isn't quite obvious to me how you would do that in this case. Instead, the problem seems to be caused because you made statics in the cache like getGroupByID() that have NOTHING to do with the cache at all and are creating these circular dependencies. That seems to be the core problem. If you remove those from cache.js, then that will free up cache.js to just do its job generically and you can remove the two require() statements causing the circular dependency from them.
Then, the question comes where to put those statics. These seem to be a bit of design problem. You're apparently trying to have person.js and group.js be peers that don't know anything about one another. But, you have methods where that isn't really true. sayHello() in person.js ends up calling into group.js and sayPeople() in group.js ends up calling into person.js.
I would suggest that you need to make a new common base class for group.js and person.js that contains shared implementation and perhaps that's where you can put those statics. I don't exactly understand what the underlying code is trying to do to know for sure that this is the best solution, but the idea is that you make person.js depend upon its base class rather than person.js depend upon group.js, thus breaking the circular dependencies.

Related

How can multiple Imported classes call each others methods?

I have this index.js file:
import GameController from './Controllers/GameController'
import LandController from './Controllers/LandController'
const GAME = new GameController();
const LAND = new LandController();
And in this class, I want to call a function every second using setInterval:
export default class GameController {
tickCount = 0;
tick = setInterval( () => {
this.Tick()
}, 1000);
Tick () {
LAND.updateLand();
this.tickCount++;
}
}
But I get this error:
Uncaught ReferenceError: GAME is not defined
I'm new to webpack, I had this working in vanilla javascript but I'm still learning, so any help would be greatly appreciated! Thank you
EDIT:
Updated GameController class based on bravo's answer, however now I get the error:
Uncaught ReferenceError: LAND is not defined
How can I make it so multiple classes can call each others methods?
You started using modules and classes, so you are moving to OOP (Object Oriented Programming). This means that you should forget about scripting, where you declare globals, everything manipulates everything, and start reasoning in a structured, hierarchical, and isolated fashion (where you have objects responsible for specific tasks).
You started properly by understanding what isolation is. You created an autonomous class to control the land. Then you need a game controller, which controls the land (and anything else, but we will come back to this lather).
Follow the reasoning; Game controls Land, in OOP this would translate in either Game receives Land or Game creates Land. Which of two to choose depends on the specific use case.
Game creates Land
// ./Controllers/GameController.js
import LandController from './Controllers/LandController'
export default class GameController {
tickCount = 0;
land = new LandController();
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.land.updateLand();
this.tickCount++;
}
}
// index.js
import GameController from './Controllers/GameController'
const GAME = new GameController();
Here you can see how Game creates Land, this is useful but reduces your access to the Land constructor (assuming you do not want to change Game to configure land)
Game receives Land
// ./Controllers/GameController.js
export default class GameController {
tickCount = 0;
land;
constructor(land) {
this.land = land;
}
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.land.updateLand();
this.tickCount++;
}
}
// index.js
import GameController from './Controllers/GameController'
import LandController from './Controllers/LandController'
const LAND = new LandController();
const GAME = new GameController(LAND);
Here you have access to both constructors and your code is more maintainable. Game is not concerned about Land (what is, what it has in or where it came from) as long as it has a method updateLand() that has to be called at an interval.
More than land
The second solution allows you to implement a more versatile Game. Assume your Land has a method called update() rather than updateLand().
// ./Controllers/GameController.js
export default class GameController {
tickCount = 0;
children;
constructor(children) {
this.children = children;
}
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.children.forEach(c => c.update());
this.tickCount++;
}
}
And then you can go beyond Land, maybe have a Character or more Land, or anything else.
// index.js
import GameController from './Controllers/GameController'
import CharController from './Controllers/CharController'
import LandController from './Controllers/LandController'
const LAND1 = new LandController();
const LAND2 = new LandController();
const CHAR = new CharController();
const GAME = new GameController([ LAND1, LAND2, CHAR ]);
This is how you can start reasoning to structure your OOP software properly; objects that are concerned about tasks and other objects.
When an object is not concerned about another object it should not have access to it (so no globals)
When an object is responsible for another object it should have direct access (receive them from the constructor or a setter) or directly instantiate them (create them).
If you really want to make the instances global, which I would not recommend because there are normally are better methods then using global variables, you should setup the variables in the window object
window.yourNamespace = {};
window.yourNamespace.game = new GameController();
window.yourNamespace.land = new LandController();
Then you can access both objects everywhere you like with
window.yourNamespace.game
window.yourNamespace.land
Answering your question about a non global solution:
I can only guess what your application will do but it sounds like a game. So GameController should be the main class. From this class you should instantiate all other classes like the LandController(). In that case you can access the LandController of course from the GameController. If you need an object in a class you should give the object as an parameter. E.g.
Class GameController()
{
landController = new LandController();
… here all needed methods for the class …
// Example if you need LandController in an other Class
exampleMethod()
{
Let otherClass = new OtherClass(this.landController);
}
}
You can do it this way:
We may use a constructor for that.
export default class GameController {
tickCount = 0;
tick = null;
constructor() {
this.tick = setInterval(() => {
this.Tick()
}, 1000);
}
Tick () {
LAND.updateLand();
this.tickCount++;
}
}

ES6 Is it ok to use functions to make instances accessible to modules shared between many pages?

I have 2 webpages, let's call them A and B.
They are mostly different but sometimes share some specific ES6 modules.
My directory structure is:
/A.js
/B.js
/A/main.js
/A/store.js
/B/main.js
/B/store.js
/commun/mymodule.js
store.js is a simple singleton object I use to easily share data across modules.
mymodule has the exact same logic in both A and B pages, the difference is the instance it uses is different. mymodule mainly contains functions and looks like this:
let objectFromAorB = null; // Will be the instance from A or B
let getItemStyle = null; // Will be a function set from A or B
const items = {}
function initItem(id) {
// Fill item
items[id] = [...];
}
function focusOnItem(id) {
const item = items[id];
// Do stuff with item
// [...]
objectFromAorB.refresh(); // HERE: objectFromAorB
}
function setItemVisible(id) {
const item = items[id];
// Do stuff with item
// [...]
item.setStyle(getItemStyle(id)); // HERE: getItemStyle
}
// HERE: Setters
function setObject(extObj) {
objectFromAorB = extObj;
}
function setStyleFunction(fct) {
getItemStyle = fct;
}
export { setObject, setStyleFunction, initItem, focusOnItem, setItemVisible };
In main.js from A and B I do things like this:
import Store from './store';
import * as Mymodule from '../commun/mymodule';
const anImportantObject = [...];
Mymodule.setObject(anImportantObject);
Mymodule.setStyleFunction(function(id) {
const color = Store.element[id]?.color;
return { color: color, [...] };
});
I have to share the anImportantObject instance from A/main.js or B/main.js to common/mymodule.js, and I also have to use a function to define how the style is obtained because the data comes either from A/store.js or B/store.js.
I'm struggling with this because I find it difficult to read, difficult to scale, and I am pretty sure there are a lot better ways to deal with this as it should be a common problem. But after a large look over the Internet I didn't find my solution.

Is it better to have one setter method or multiple for objects with a fixed number of fields?

I have a class with a member variable of type object. This object has a fixed number of fields. I'm trying to decide if I should use one setter function or multiple to mutate these fields.
To make the question more concrete, I've written the following class to model a simple organizational management structure in two different ways:
Multiple Setter Functions
class Management {
constructor() {
this.numberOfManagers = 100;
this.salaryDetails = {
regionalManagerSalary: 80000,
stateManagerSalary: 110000,
executiveManagerSalary: 200000
};
}
setRegionalManagerSalary(salary) {
this.salaryDetails.regionalManagerSalary = salary;
}
setStateManagerSalary(salary) {
this.salaryDetails.stateManagerSalary = salary;
}
setExecutiveManagerSalary(salary) {
this.salaryDetails.executiveManagerSalary = salary;
}
}
const management = new Management();
management.setRegionalManagerSalary(100000);
management.setStateManagerSalary(120000);
management.setExecutiveManagerSalary(210000);
One Setter Function
class Management {
constructor() {
this.numberOfManagers = 100;
this.salaryDetails = {
regionalManagerSalary: 80000,
stateManagerSalary: 110000,
executiveManagerSalary: 200000
};
}
setManagerSalary(typeOfManagerSalary, salary) {
this.salaryDetails[typeOfManagerSalary] = salary;
}
}
const management = new Management();
management.setManagerSalary('regionalManagerSalary', 100000);
management.setManagerSalary('stateManagerSalary', 120000);
management.setManagerSalary('executiveManagerSalary', 210000);
Would implementation 1. be better or would implementation 2. be better?
I will always consider the higher readability approach if it doesn't trade off a lot of writability. The second one is more generic but the first one is more clear.
So I think the first one is better as it is more clear that there are only 3 fields in salaryDetails. Just consider you are not the author and when you see the second one, you have no idea how many fields there will be in salaryDetails as someone could just call management.setManagerSalary('someNewSalary', 100000); somewhere to add some new fields.
Unless you have more than one Management objects and each of them might have some specific fields under salaryDetails, I don't think the second approach is better.
My recommendation would be to have one method say setSalaryDetails which will take salary object as input which you can set directly. Something like -
setSalaryDetails(salaryDetails){this.salaryDetails = salaryDetails; }
When you invoke this method you can appropriately create this object with those fields set that you will pass to it.
It is better to not have any setter at all. Initialize all the required properties in the constructor (let the constructor require arguments for this).
The code you posted it not OOP but procedural code under disguise. If you want to make it OOP then the code must stay where the data is, i.e. in the class.
As a first improvement step, your code should look like this:
class Management {
constructor(nb, regionalSalary, stateSalary, executiveSalary) {
this.numberOfManagers = nb;
this.salaryDetails = {
regionalManagerSalary: regionalSalary,
stateManagerSalary: stateSalary,
executiveManagerSalary: executiveSalary
};
}
}
const management = new Management(100, 100000, 120000, 210000);
Shorter, much cleaner, less error prone.
The fragment you posted is not enough to tell what you want to achieve but the new signature of the constructor tells me that you are mixing too many responsibilities into a single class.
A cleaner design is to use separate classes for each manager type:
/* abstract */ class Manager {
constructor(salary) {
this.salary = salary;
}
}
class RegionalManager extends Manager { }
class StateManager extends Manager { }
class ExecutiveManager extends Manager { }
class Management {
constructor(nb, regionalManager, stateManger, executiveManager) {
this.numberOfManagers = nb;
this.managers = {
regionalManager: regionaManager,
stateManager: stateManager,
executiveManager: executiveManager
};
}
}
const management = new Management(
100,
new RegionalManager(100000),
new StateManager(120000),
new ExecutiveManager(210000)
);
You can now implement the logic common to all manager types in class Manager, the logic that is specific to each manager type in its class and the logic that handles the managers by number, without caring about their individual characteristics, in class Management. This way the code uses less if and switch conditions, is easier to read, understand and develop further.

Interfaces with stampit 2

As mentioned in the interface section of 'Programming javascript applications' you can implement interfaces with stampit. When reducing the unnecessary stuff from the example, I end up with something like this:
const fooBarInterface =
stampit()
.methods(
{
foo: () => {
throw new Error('connect not implemented');
},
bar: () => {
throw new Error('save not implemented');
}
}
)
Excerpt from an interface definition:
If your class claims to implement an interface, all methods defined by
that interface must appear in its source code before the class will
successfully compile.
So now putting the interface in use
const fooBarImplementation =
stampit()
.compose(fooBarInterface)
.methods(
{
foo: () => {
// implement me
}
}
}
)
Now, when composing an object from the stamp, there should be an error because bar is not implemented by fooBarImplementation. It isn't, and I fear that it's pretty hard to get something like this in place because there is no compiling at all.
So, my question is: Am I doing it wrong or is this half baked thing what Eric Elliott calls an 'Interface'?
The module you've created is great! You really know stampit well.
Although, in JavaScript I would recommend to go a different path. Namely, check if method is present.
if (obj.bar) kangaroo.bar();
And remove the fooBarInterface entirely. But if you need to check the method presence while creating the object, then you should do it similar to your module.
var ValidateFooBar = stampit()
.init(function() {
if (!_.isFunction(this.foo)) throw new Error('foo not implemented');
if (!_.isFunction(this.bar)) throw new Error('bar not implemented');
});
And use it:
const fooBarImplementation = stampit()
.compose(ValidateFooBar)
.methods({
foo: function() {
// implement me
}
});
Will throw: Error: bar not implemented

My ReactJS flux App has too many 'actiontype' constants, how can I separate and namespace them?

I'm building a flux app that involves many different types of data, and the CRUD style modification of the resources. This leads to the a large number of ActionTypes. Most of them follow the same pattern, REQUEST_ENTITY, REQUEST_ENTITY_SUCCESS, REQUEST_ENTITY_ERROR, and so on.
How do I separate them into namespaced constants?
Ideally instead of accessing them like:
ActionTypes.REQUEST_ENTITY
I could access them in a more sane way like,
ActionTypes.entity.REQUEST
Why not skip the constants, and just use the string values? Sure, you may mistype one from time to time, but you could just as easily mistype the constant names, right? Your unit tests will fail in the same place, either way, and you'll know what's wrong.
Without compile-time checking, the main value of these kinds of constant lists is that the code is a bit more self-documenting, but if you're that consistent in your naming conventions, it might not be worth the extra effort to write them all out as constants?
(That was kind of a non-answer, I guess, but I've had this same conversation with others, so probably worth adding to the discussion here, too.)
You could simply merge multiple objects (perhaps exported from different files) into ActionTypes.
// entity_actions.js
module.exports = {
entity: {
REQUEST: "entity.REQUEST",
DELETE: "entity.DELETE",
}
};
// user_actions.js
module.exports = {
user: {
REQUEST: "user.REQUEST",
DELETE: "user.DELETE",
}
};
// actions.js
var entityActions = require("./entity_actions");
var userActions = require("./user_actions");
var ActionTypes = Object.assign({}, entityActions, userActions);
You can use something like Underscore#extend or object-assign if Object.assign isn't available in your environment.
I personally use a small module I called nestedKeyMirror that takes a big nested object and automatically generates values based on the nesting:
function nestedKeyMirror(obj, namespace) {
namespace = namespace || [];
for (key in obj) {
if (obj.hasOwnProperty(key) && obj[key] === null) {
obj[key] = namespace.concat([key]).join(":");
} else if (obj.hasOwnProperty(key) && typeof obj[key] === "object") {
obj[key] = nestedKeyMirror(obj[key], namespace.concat([key]));
}
}
return obj;
}
For example, in one app, I have the following action types defined:
var actionTypes = nestedKeyMirror({
LAYOUT: {
RESIZE_PANE: null
},
CANVAS: {
SET_PROPERTY: null
},
SHAPES: {
ADD: null,
SET_PROPERTY: null,
SEND_BACKWARD: null,
SEND_FORWARD: null,
SEND_TO_BACK: null,
SEND_TO_FRONT: null
},
SELECTION: {
SELECT: null,
DESELECT_ALL: null
},
HISTORY: {
ADD: null,
SELECT_INDEX: null
}
});
This would give, e.g., actionTypes.SHAPES.ADD with an automatically-generated string value of "SHAPES:ADD". This technique can be combined with the object-merging strategy, above, to easily create deeply nested action type constants.
[Update: it looks like someone recently released a package that does the nested key mirroring on npm: keymirror-nested]
If the problem is that all your action types look similar, you could easily create a function to generate them (ES6 computed property syntax used here):
function generateActionType(type, base) {
return {
[base]: `${base}_${type}`,
[`${base}_SUCCESS`]: `${base}_${type}_SUCCESS`,
[`${base}_ERROR`]: `${base}_${type}_ERROR`
};
}
ActionTypes.entity = {};
Object.assign(ActionTypes.entity, generateActionType("ENTITY", "REQUEST"));
Object.assign(ActionTypes.entity, generateActionType("ENTITY", "DELETE"));
ActionTypes.entity.REQUEST_SUCCESS === "REQUEST_ENTITY_SUCCESS";

Categories