import {User} from './moduleB';
export class Base {
static foo() {
User.doSomething()
}
}
moduleB.js:
import {Base} from './moduleA';
class User extends Base {
static doSomething() {
// ...
}
}
The issue is that this does not work because of the circle imports. I get Class extends value undefined is not a constructor or null error. In Python, I would simple do:
export class Base {
static foo() {
import {User} from './moduleB';
User.doSomething()
}
}
But here it does not work. I got it to work by doing;
export class Base {}
(function () {
import {User} from './moduleB';
Base.foo = function foo() {
User.doSomething();
};
})();
But I do not really understand why this works. And if this is really the best one can do.
When you import moduleA in moduleB, moduleA isnāt evaluated yet (it āpausedā at import { User } from "./moduleB";), so Base is still undefined. In your last example, moduleA can be evaluated completely before moduleB is imported, which means that Base isnāt undefined in moduleB (because you donā t depend on moduleB to define Base). Itās similar to what you did in the third code block. Why doesnāt it work if you import moduleB in foo?
In my experience, when you encounter these issues itās not a code problem, but a problem of abstraction and responsibility. You are giving classes too much responsibility, beyond what they should have access to for the sake of convenience. The end result is an intertwined web of dependencies with few clear boundaries and fuzzy methods. Like calling Food.getPeopleEatingPizza, you make the Food class also responsible for finding people.
This can be solved by going āone level upā, having files/classes responsible for the intersection of multiple domains, that import both classes and does meaningful things to them. Preferably, you have very simple data models, that can be easily imported by such classes that transform them to the different needs you might have, instead of having āfatā models stirring all kind of different pots.
If you have a class called āBaseā, youād really expect it to form the ābaseā of other things to build upon. If your base starts reaching up and grabbing things it should not know about, itās no longer a base.
Iāve actually had a similar situation recently with my socialize:commentable package when trying to port it to NPM for React Native. Ultimately I decided that the cleanest solution was to put the dependent classes in the same file and export them both from there
Yea. In some pure OOP world maybe. To me it is simply āa bunch of logic all other classes should haveā. And then that logic also has something where you need other logic. In my case for example, Base is something which all documents should have, and users are documents. But also all documents should have permission checks. And you need users for that. And you have a loop.
Okay, so you have documents, which have Base functionality, but need User checks. Thatās where the example given seems a bit misleading, as you are calling static methods without any arguments. If it was just that, you would just need to re-structure the hierarchy as I suggested.
But if you are modifying instance variables, or passing them, then youād obviously need to do something else. Say you took the user doc as an argument to the validate method, and preformed static checks on that user object? Would that work? Then you would not need to import the user in Base, you could for example import a module like Auth and do Auth.userCanModify(user, doc) or something.
It all depends on how you are using the docs I guess, do you have an example which is more in line with the usage?
Another alternative to this is to have another file that imports Base and User, and defines the functions there, as you can be sure both modules have been resolved. so a file like ābase_static_methodsā thatās just imported as a part of the app, containing
import {User} from './moduleB';
import {Base} from './moduleA';
Base.foo = function foo() {
User.doSomething();
};
Base.bar = function bar() {
User.doSomethingElse();
};
This was an approach I took in my app at some point, and regretted it later as I was giving away too much responsibility to this class. I later abstracted this functionality to create fewer dependencies.
So looking at the methods being used, you have just 1 static method in User that you need, hasPermission.
So, could you move this to a āpermission.jsā living alongside base.js, which had only this static method, and call it like Permission.userHasPermission(permssion, user) ?
that way, both User and Document could import it, and you woud not need to import User from Document, and you could move the static functions in Document back into the class.
Another way would be moving the static hasPermission from User to Base. That would solve it, right? As the static hasPermission uses nothing in User anyway, it just inspects the user object provided.
Oh, in fact it works! I figured it out. Because I was copy-pasting around, WebStorm automatically added import on top as well and that one was failing. But undo also removed it so I didnāt notice. So yea, just having imports inside methods works. So cool.
But it is static, so the document instances donāt really have itā¦ And since all docs have canUser and restrictQuery, which both use hasPermission, all documents are using it by proxy anyway. I donāt mean to be argumentative, I guess Iām just not seeing the whole and the reason for having that method in User.