I want a JavaScript class that can conditionally add additional methods into itself from separate files. The idea is to separate different concerns of the app into more manageable self-contained modules that nevertheless can interact with the methods in the mother app class. Therefore, the additional methods in the separate file must be able to reference the methods and variables in the main class. See the code sample below.
I looked at a lot of different solutions, but all of them have a downside for what I'm wanting.
I could do new Uploads(), but I could not find a way for methods in Uploads.js to reference methods in the main App class
I could use extends, but this would not allow for conditional extension AFAIK
I could just define new methods into the prototype itself, but this would mean that the external file would need to "know" about the class it's going to be used in, which doesn't make it widely reusable.
The best I have so far is the following:
app.js
const Uploads = require("./Uploads.js");
const config = { hasUploads: true }; // Probably loaded from a file
class App {
constructor() {
/* Only include the separate module if the config says so */
if(config.hasUploads) {
Object.assign(this, Uploads);
}
}
foo() {
/* Something */
}
}
Uploads.js
module.exports = {
bar() {
this.foo();
}
};
It works, but I don't know if this is the best solution;
There's no constructor, so if Uploads.js needs to do some setup, app.js needs to contain the logic to do so (or at least know to call some uniquely named faux constructor method), which doesn't seem ideal.
If Uploads.js contains a method with the same name as app.js or any other possible module being loaded, they will be overwritten, leading into unexpected behaviour.
The Uploads.js is an object of functions, whereas app.js defines a class. Ideally (though I guess not necessarily) for code manageability they should both use the same syntax.
Is there a better/cleaner/nicer way of doing this?
Instead of trying to perform some kind of crazy multi inheritance, why not try embracing composition? Its very good for solving these kinds of problems.
class App {
constructor(modules) {
if (modules.uploads) {
this.uploads = modules.uploads(this);
}
}
foo() {
console.log('foo!');
}
}
class Uploads {
constructor(context) {
this.context = context;
}
method() {
this.context.foo();
}
}
const app = new App({ uploads: (ctx) => new Uploads(ctx) });
app.uploads.method();
You can get really fancy with this and use builders to configure apps with specific types of modules.
Depending on your anticipated complexity, you might want to think about using event buses, mediators, or commands to decouple things from the host itself.
One option to fix overwriting an existing method from the uploads file is to assign new methods in a loop and check for duplicates (Object.assign is not ideal in this case) and only add updates once:
const Uploads = {
bar() {
this.foo("called from bar");
}
};
const config = { hasUploads: true, // Probably loaded from a file
configured: false
};
class App {
constructor() {
/* Only include the separate module if the config says so */
if(config.hasUploads && !config.configured) {
const proto = this.constructor.prototype;
const methods = Object.keys(Uploads);
methods.forEach( name=> {
if( proto[ name] ) {
throw new Error( "App already has method " + name);
}
proto[name] = Uploads[name];
});
config.configured = true;
}
}
foo(arg) {
/* Something */
console.log( arg );
}
}
const app = new App();
app.bar();
A better (cleaner) alternative might be to add updates to the class before calling its constructor, using a static class method because its this value is the constructor function. Tested example:
static addMethods(uploads) { // inside class declaration
const proto = this.prototype;
for (const [name, method] of Object.entries(uploads)) {
if( proto[name]) {
throw new Error("App already has a ${name} method");
}
proto[name] = method;
}
}
to be called as needed by
if( config.hasUploads) {
App.addMethods( Uploads);
}
Related
I'm in the process of converting a JavaScript project to Typescript.
I have an old module file that was require()-d from the main.js file of the project.
Here is the simplified example:
//required_module.js
const foo = require("bar");
module.exports = {
start(myvar) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type) {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type) {
return this.myvar.asd(type);
}
};
As you can see, this is just a simple JS module file (not a class), that uses this many times, and it just works as expected.
However as I try to convert this module to a TypeScript module without creating a class from it, I get different kind of errors at the this references as I try different approaches, like:
Object is possibly 'undefined'.ts(2532)
'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
An outer value of 'this' is shadowed by this container.
Is the only solution to create a class or TypeScript also allows using this outside of classes in a specific way?
Based on the helpful comments, the answer is:
You probably don't have to convert it to a class, but you'd be better off: you are going to run into issues dynamically adding properties (like myvar) or calling methods on untyped objects. Typescript doesn't want you treating JS objects as a random grab-bag of properties: there's already another data structure for that- Jared Smith
I'd focus less on converting to typescript and more on converting to modern module syntax. If you used named exports instead of object methods, you wouldn't have these issues. You would however have a stateful, static, module-scoped myvar variable (as you currently do), which should be avoided. Instead of the singleton, a class that can be instantiated multiple times might be the better approach.- Alex Wayne
The example, simply converted code would look like something like this in TypeScript:
import foo from 'bar';
export default {
myvar: 0,
start(myvar: number) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type: 'str1' | 'str2') {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type: string) {
//...
}
};
I've got a Tag class and a TagCollection class to store multiple tags. Now the TagRepository class needs to construct a new Tag object to return. But when I try to create a new Tag inside the TagRepository it returns an error:
Tag is not defined
This is how I include all the classes in the main JavaScript files:
const Tag = require('./class/Tag.js');
const TagCollection = require('./class/TagCollection.js');
const TagRepository = require('./repository/TagRepository.js');
How do we usually deal with this? I could just include the needed classes inside the constructor of the class that requires them. But that seems messy when I have to include multiple classes.
The only other solution I could think of is making the needed classes global, reading online it seems like that is considered bad practice. Below I've included all the classes
Tag.js
module.exports = class Tag {
constructor() {
this.id;
this.name;
}
setId(id) {
this.id = id;
}
setName(name) {
this.name = name;
}
getId() {
return this.id;
}
getName() {
return this.name;
}
}
TagCollection.js
module.exports = class TagCollection {
constructor() {
this.tags = [];
}
addTag(tag) {
this.tags.push(tag);
}
setTags(tags) {
this.tags = tags;
}
getTags(tags) {
return this.tags;
}
}
TagRepository.js
module.exports = class TagRepository {
constructor(conn) {
this.conn = conn;
}
getAll(callback) {
let tempTagCollection = new TagCollection;
this.conn.query(`SELECT \`id\`, \`name\` FROM \`tag\` WHERE 1`, function (error, tags) {
tags.forEach((tag) => {
//Create single tag
let tempTag = new Tag;
//Set properties
tempTag.setName(tag.name);
tempTag.setId(tag.id);
//Add single tag to collection
tempTagCollection.addTag(tempTag);
})
callback(tempTagCollection);
})
}
}
The only other solution I could think of is making the needed classes global, reading online it seems like that is considered bad practice.
You're right, making global variables should be avoided as much as possible as it promotes brittle and hard to debug code.
You can think of each file as a module. I personally like to keep one file per class, so I can treat the class itself as a module. In each module, you should require every class that you depend on.
So I'll use a classic Animal/Cat/Dog example:
//Animal.js
module.exports = class Animal { ... }
//Cat.js
const Animal = require('./Animal');
class Cat extends Animal { ... }
//Dog
const Animal = require('./Dog');
class Dog extends Animal { ... }
In NodeJS, even though both Cat & Dog requires Animal, Animal.js is only ever executed once. So each module that requires Animal will obtain the same Animal class.
I could just include the needed classes inside the constructor of the class that requires them.
I would also avoid doing this. Using require in a constructor, even though a require'd file will only execute the file the first time it has been required, it still goes through the node file resolution algorithm which is an expensive process and may cause performance bottlenecks. Generally it is best to have your require statements outside of constructors or functions. Keep them at the top of the file, where all the requires will run once when the application loads.
As you can now see inside the TagRepository it requires two classes, the Tag and TagCollection class how would I go about this?
Inside TagRepository.js you just need to simply have 2 include statements, one for each file, see below.
const Tag = require('./Tag');
const TagCollection = require('./TagCollection.js');
// Both Tag and TagCollection is now usable
class TagRepository { ... }
Further reading on NodeJS modules can be found here
https://nodejs.org/dist/latest-v10.x/docs/api/modules.html#modules_modules
You need to 'require' every class you need in every file you need it, so unlike (for example) PHP you can't just require everything once per program.
I am confused while encountering this over-looked question like how they are different ?
Module.exports = {
search_companies(req, res) {
//some ops
},
get_details(req, res) {
//some ops
}
};
vs
class MainContrller {
search_companies(req, res) {
//some ops
},
get_details(req, res) {
//some ops
}
}
module.exports.MainController = MainController;
The first one exports an object with the function search_companies and get_details. So you can call these to function on the object that is exported.
The second one exports a class MainController with the functions search_companies and get_details. Here you have to create an instance of MainController to be able to call those two functions on the instance.
You use the first syntax if you only need one instance of that object through the whole project. It's like a singleton or like static, but without the need to define an actual class for it.
And you use the second one if you need multiple different instances of MainController.
A module is supposed to be used like:
const { search_companies } = require('...');
A class is supposed to be used like:
const { MainController } = require('...');
const { search_companies } = new MainController();
MainController in this case is a bad practice because it mimics the usage of classes in other languages without taking the specifics of JavaScript into account.
MainController doesn't benefit from being a class if this instance is ignored and doesn't lose in functionality when a class is refactored to separate functions.
Classes aren't glorified namespaces in JavaScript; there are modules that already serve this purpose. If there's a need for a namespace and no need for class instance, a module can be used as a rule of thumb.
Is there any point in repeating this pattern for every property in JavaScript?
class Thing {
get myProp() {
return this._myProp;
}
set myProp(value) {
this._myProp = value;
}
}
I understand that getters/setters can be useful if you're doing additional work in the methods, but recreating the basic functionality here seems like needless repetition. If I instantiate, I can still manipulate the backing property (._myProp) directly, so I feel like I could just as easily leave these out and perform my assignment and access in the more typical, ad-hoc fashion.
I suppose you could argue that defining the interface this way (with the underscore-prefixed property name) signals to users that it's not meant to manipulate the property, but that seems like a flimsy reason for potentially dozens of these.
In compiled languages, it's common for people to do this. This is because in those languages, assigning to a field and invoking a setter may be identical to the programmer, but they compile to two completely different operations.
If I wanted to add a side effect for setting a field in a C# class, for example, and that field was being set directly instead of through a setter? Changing it to a setter would cause some issues. I wouldn't have to rewrite any of the consuming code, but I would have to recompile all of it. This is, of course, a huge problem if your public releases are compiled.
JavaScript is subject to no such considerations, though, so making everything into a getter/setter prematurely is kind of silly. If you see someone doing this, more than likely you're dealing with someone who learned the convention from another language and carried it into JavaScript, without thinking a whole lot about why.
Using an accessor property in the fashion you describe (set and retrieve a "background" data property) is virtually semantically identical to using a data property directly. There are some differences: the accessor property will exist on instance's prototype, rather than on the instance directly (though the instance will have the "background" data property), but this won't really affect anything unless you are doing advanced introspection on your class instances.
The only advantage is ease of modifying the code if you want to introduce more sophisticated accessor behavior in the future. If you forsee a need to add accessor behavior, use this pattern to save yourself time in the future.
Property accessors are useful to provide side effects or change original behaviour:
class Thing {
get myProp() {
console.log('myProp was read');
return this._myProp;
}
set myProp(value) {
if (!value)
throw new Error('myProp cannot be falsy');
this._myProp = value;
}
}
There is no point in myProp getter/setter pure abstraction:
class Thing {
get myProp() {
return this._myProp;
}
set myProp(value) {
this._myProp = value;
}
}
If I instantiate, I can still manipulate the backing property
(._myProp) directly,
If private states are what you are looking for you can still use a weak map.
(function(scope) {
"use strict";
const prop = new WeakMap();
scope.Foo = class {
constructor() {
prop.set(this, {});
Object.seal(this);
}
get bar() {
return prop.get(this)._bar;
}
set bar(value) {
return prop.get(this)._bar = value;
}
}
}(this))
const f = new Foo;
f.bar = "bar";
f._bar = "_bar";
console.log(f.bar);
console.log(f._bar);
get and setters are also useful when implementing MVC, you can trigger events on property change.
(function(scope) {
"use strict";
const prop = new WeakMap();
scope.Foo = class {
constructor() {
prop.set(this, {});
prop.get(this)._target = new EventTarget
Object.seal(this);
}
get bar() {
return prop.get(this)._bar;
}
set bar(value) {
prop.get(this)._bar = value;
prop.get(this)._target.dispatchEvent(new CustomEvent('change', {
detail: value
}));
}
addEventListener(event, listener) {
prop.get(this)._target.addEventListener(event, listener)
}
}
}(this))
const f = new Foo;
f.addEventListener('change', function(event) {
console.log("changed!", event.detail);
});
f.bar = "bar";
I'm trying to explore using ES6 classes instead of how we do it currently, using the Function.prototype means. Currently our API looks like:
var myclass = createClass('MyClass', {
test : function() {}
});
We iterate through the object and apply those properties onto the Function that we return, basically a prettier way than to do so that it's more inline with other programming languages of sorts:
function MyClass() {}
MyClass.prototype.test = function() {};
We also cache the class onto an object where name is the key and the function is the value for use throughout our application. The class name can be namespaced so you can have My.Cls and it will split by the period and then cache it onto the manager but it also can be retrieved via window.My.Cls.
Looking into ES6 classes, I don't see how I can keep the createClass function. Would love something like:
function createClass(name, config) {
return class name config;
}
I didn't expect it to work and it doesn't.
Two issues I have here:
How can I create a class using a variable as the class name?
How can I create a class and assign the properties via the config object argument?
Not sure this would be possible. We don't plan on keeping the createClass, we hope to keep it for now and upgrade our legacy "classes". I'd like to start using ES6 classes but not break the whole app for however long it'll take us to fully upgrade.
The only good upgrade route is to refactor the property hashes into proper classes. You can start that work and keep using your hash-based classes in the meantime, which will lighten the requirement to do it all at once.
If you have a limited number of "class" name:config pairs -- which you should for maintainability reasons -- then you can replace createClass with an implementation that does:
class Foo { ... }
class Bar { ... }
let classes = {'Foo': Foo, 'Bar': Bar};
function createClass(name, config) {
if (classes[name]) {
return classes[name];
}
// old impl
}
This will ignore the config if a "real" implementation exists, but keep using the legacy behavior if you haven't replaced the class. If it is, you can implement createClass more like:
function createClass(name, config) {
if (classes[name]) {
return new classes[name](config);
}
// old impl
}
and pass the config arguments into the class ctor. In this case, you may want to filter out function properties (methods) first, as the class probably implements them already. Something like:
function createClass(name, config) {
if (classes[name]) {
let fields = Object.keys(config).filter(key => {
return typeof config[key] !== 'function';
}).map(key => config[key]);
return new classes[name](fields);
}
// old impl
}