Use a certain object instance in multiple modules without global variables [duplicate] - javascript

Will the snippets below produce new instance every time it's imported?
// 1st implementation
class ConnectionManager {
...
}
export default new ConnectionManager();
// 2nd implementation
class ConnectionManager {
...
}
const connectionManager = new ConnectionManager();
export default connectionManager;
If yes, how can I get the same instance in every import?

ES6 modules follow single instance pattern. That is, the instance is created when the module is loaded.
Here is an article about it.
// File: yolo.js
class Yolo {}
export let yolo = new Yolo();
// File: laser.js
import { yolo } from "./yolo.js";
// yolo is a single instance of Yolo class
// File: cat.js
import { yolo } from "./yolo.js";
// same yolo as in laster.js

It should be the same.
The following example uses both the implementations, imports them into 2 different files, and imports them all into single index file. Everytime an instance is created, we generate a random value for the class, and log its creation.
// ConnectionManagerImpl1.ts
class ConnectionManagerImpl1 {
public value;
constructor() {
this.value = Math.random().toString(36).substring(7);
console.log(`New ConnectionManagerImpl1 instance created: ${this.value}`)
}
}
export default new ConnectionManagerImpl1();
// ConnectionManagerImpl2.ts
class ConnectionManagerImpl2 {
public value;
constructor() {
this.value = Math.random().toString(36).substring(7);
console.log(`New ConnectionManagerImpl2 instance created: ${this.value}`)
}
}
const connectionManagerImpl2 = new ConnectionManagerImpl2();
export default connectionManagerImpl2;
// import1.ts
import connectionManagerImpl1 from './ConnectionManagerImpl1';
import connectionManagerImpl2 from './ConnectionManagerImpl2';
export { connectionManagerImpl1, connectionManagerImpl2 };
// import2.ts
import connectionManagerImpl1 from './ConnectionManagerImpl1';
import connectionManagerImpl2 from './ConnectionManagerImpl2';
export { connectionManagerImpl1, connectionManagerImpl2 };
// index.ts
import * as import1 from './import1';
import * as import2 from './import2';
console.log(import1)
console.log(import2)
console.log("Done")
Ran the above setup using tsc --module 'commonjs' * && node index.js
Output:
New ConnectionManagerImpl1 instance created: ddt3re
New ConnectionManagerImpl2 instance created: uv5z6
{ connectionManagerImpl1: ConnectionManagerImpl1 { value: 'ddt3re' },
connectionManagerImpl2: ConnectionManagerImpl2 { value: 'uv5z6' } }
{ connectionManagerImpl1: ConnectionManagerImpl1 { value: 'ddt3re' },
connectionManagerImpl2: ConnectionManagerImpl2 { value: 'uv5z6' } }
Done
As you can see, only 1 instance of ConnectionManagerImpl1 and ConnectionManagerImpl2 were created. So, both the implementation should create only 1 instance.

The export statement is used when creating JavaScript modules to export functions, objects, or primitive values from the module so they can be used by other programs with the import statement.
There are two different types of export, named and default. You can have multiple named exports per module but only one default export.
export default class ConnectionManager { .. }
Or
class ConnectionManager {
...
}
export default connectionManager;

Related

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.

Should I new a utils class in es6?

I have declared a class, like:
export default class Utils {
static deepClone(): object {
return {}
}
}
so when I want to use deepClone, I can:
// First One
import Utils from './Utils';
export default class Element {
constructor(){
this.utils = new Utils()
}
create(){
return this.utils.deepClone()
}
}
or:
// Second One
import Utils from './Utils';
export default class Element {
constructor(){
this.utils = Utils
// this is an optional
// in my child class I need't to import Utils
// I can use this.utils.deepClone() directly
}
create(){
return Utils.deepClone()
}
}
I wonder which is a better way to imply Element class
Looking forward to your reply, I can’t thank you enough
The second way is more correct but it has a problem that you should create an instance of Utils class to use each property which isn't a static method.
The output of a class that you didn't create an instance is a function instead of an object of prototypes.
./Utils.js
export default class Utils {
static deepClone(): object {
return {}
}
public deepExtend() {
// deepClone code
}
}
Use in another file:
import Utils from './Utils';
export default class Element {
constructor(){
this.utils = new Utils();
}
create(){
return Utils.deepClone()
}
extend(){
return this.utils.deepExtend()
}
}
I would return export new of the Utils and I will pass it in the constructor of your Element as an Instance, something like:
IUtils = someInterface;
export default class Element {
constructor(utils: IUtils){
this.utils = utils;
}
create(){
return this.utils.deepClone()
}
}
In these way it:
Doesn't create new instances for nothing
Uses other instances that you can pass to your Element class
Is testable

How to export an extended class without curly brackets? (Node JS)

My goal is to export an extended class called Entrance without using curly brackets (Let's say subclass).
The problem is I can't access to the subclass while I'm using default keyword on it and the browser gives me an error such as this:
Uncaught SyntaxError: Duplicate export of 'default'
Code:
// terminal.js
export default class Terminal {
constructor(output) {
this.output = output;
console.log(`This is Terminal`);
}
}
export default class Entrance extends Terminal {
constructor(output) {
super(output);
}
ticket() {
console.log(`Ticket please`);
}
}
// human.js
import Entrance from './terminal.js';
class People {
constructor(name, input) {
this.name = name;
this.input = new Entrance(this);
this.input.ticket();
}
}
const guy = new People('james');
Is this type of structure doesn't allow originally? Or did I miss something in the code?
Thanks for listening.
I adapted your project to Node.js because this is easier for testing. In the browser you can still use the .js extension but you need to reference the files as modules, not as scripts.
For Node.js use the extension .mjs for ECMAScript Modules ('import' and 'export' are a feature of ECMAScript modules).
You only need to export the identifiers you reference externally: (the 'Entrance' class).
Use 'node --experimental-modules ./human.mjs' to run it
// terminal.mjs: only 'Entrance' is exported
// no need to export 'Terminal' as long as it's name is not referenced outside
class Terminal {
constructor(output) {
this.output = output;
console.log(`This is Terminal`);
}
}
export default class Entrance extends Terminal {
constructor(output) {
super(output);
}
ticket() {
console.log(`Ticket please`);
}
}
// human.mjs
import Entrance from './terminal.mjs';
class People {
constructor(name, output) {
this.name = name;
this.input = new Entrance(this);
this.input.ticket();
}
}
const guy = new People('james');
If you want to reference also the Terminal class outside, dont use 'default' exports/imports (or create a top level object with Terminal and Entrance as members):
// terminal2.mjs
export class Terminal {
constructor(output) {
this.output = output;
console.log(`This is Terminal`);
}
}
export class Entrance extends Terminal {
constructor(output) {
super(output);
}
ticket() {
console.log(`Ticket please`);
}
}
// human2.mjs: import the whole module under the alias 'term'
import * as term from './terminal2.mjs';
class People {
constructor(name, output) {
this.name = name;
this.input = new term.Entrance(this);
this.input.ticket();
new term.Terminal(this);
}
}
// human3.mjs: using named imports which are directly usable
import { Terminal, Entrance} from './terminal2.mjs';
class People {
constructor(name, output) {
this.name = name;
this.input = new Entrance(this);
this.input.ticket();
new Terminal(this);
}
}
const guy = new People('james');
Now with default exports but encapsulated into a library object. This might be the standard way to do it, but only export the symbols you reference outside:
// terminal4.mjs: using a top level object and a default export
class Terminal {
constructor(output) {
this.output = output;
console.log(`This is Terminal`);
}
}
class Entrance extends Terminal {
constructor(output) {
super(output);
}
ticket() {
console.log(`Ticket please`);
}
}
const myLib = {Terminal, Entrance};
export default myLib;
// or just: export default {Terminal, Entrance};'
// human4.mjs
import term from './terminal4.mjs';
class People {
constructor(name, output) {
this.name = name;
this.input = new term.Entrance(this);
this.input.ticket();
new term.Terminal(this);
}
}
const guy = new People('james');
references:
'export': https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
'import': https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Node.js ECMAScript Modules: https://nodejs.org/api/esm.html
The error is shown because you are exporting both Terminal and Entrance classes as default.
if you need only Entrance class from terminal.js, remove export default from Terminal class.

making modular javascript library by importing class methods without need to the whole class

I'm trying to make a reusable javascript library for example books class and have createbook, checkforbook, modify book methods
I want to use it like so
import Library, {checkForBook, modifyBook} from 'books'
Library.createBook({
name : 'firstbook',
year: 2012
})
checkForBook('firstBook')
modifyBook('firstBook',{
name: 'secondBook'
})
my problem right now is: how to do it without need to use new keywrod , and how to use methods wihout need to make it like Library.checkForBook
and still have access to the list of books
const books = new Map(); // store the books somehow
export function createBook({ name, year }) { // make them available as import { createBook } from ...
const book = { name, year };
books.set(name, book);
return book;
}
export function checkForBook(name) {
return books.has(name);
}
export function modifyBook(name, update) {
if(!checkForBook(name))
throw new Error("Cannot modify, book doesnt exist");
Object.assign(books.get(name), update);
}
export default { // make them available as Library.createBook
createBook,
checkForBook,
modifyBook
};
I think you are looking for
// library.js
export default class Library {
…
}
// books.js
import Library from './library';
// creates a singleton:
export default const books = new Library();
export function checkForBook(name) {
return books.checkForBook(name);
}
export function modifyBook(name, value) {
return books.modifyBook(name, value);
}
// main.js
// imports the singleton, not the `Library` class
import books, {checkForBook, modifyBook} from 'books';
books.createBook({
name : 'firstbook',
year: 2012
});
checkForBook('firstBook');
modifyBook('firstBook', {
name: 'secondBook'
});

Typescript migration: Javascript "namespace" with multiple objects

I'm migrating some old js to ts. The file is of form (function implementations omitted for clarity):
// component.js
const Component = {}; // 'namespace' for components
Component.Base = function() {}
Component.A = function() {} // extends Base
Component.A.prototype.doStuffA = function() {}
Component.B = function() {} // extends Base
Component.B.prototype.doStuffB = function() {}
Component.C = function() {} // extends Base
// ... 100 other components, 2000 lines of code
export default Component;
In js, to use the file, I can do:
import Component from './component';
// 1. Instantiate one component
const compA = new Component.A();
// 2. or create multiple components
const threeComps = ['A', 'B', 'C'].map(name => new Component[name]() );
But in ts, I cannot even instantiate one component:
import Component from './component';
const compA: Component.A = new Component.A();
// tsc Cannot find namespace 'Component'
Question: What is the (quick) way to convert component.js into valid typescript, preferably keeping as many type-checks available as possible such
that
const compA: Component.A = new Component.B()
will be flagged as an error by the compiler.
I tried appending the following to the end of file:
namespace Component {
interface A {};
interface B {};
interface C {};
}
This seems to compile into correct javascript, but I would have to add all properties into interfaces. Seems tedious and violation of DRY-principle.
If you are going to migrate to TypeScript, you could immediately take advantage of the class syntax in your component.ts file:
export class Base {
}
export class A {
doStuffA() {}
}
export class B {
doStuffB() {}
}
export class C extends Base {
}
You can consume it using an import alias:
import * as Component from './component';
const a = new Component.A();
Or you can be selective with what you import:
import { A } from './component';
const a = new A();
Export Default / Modules Mixed With Namespaces
On the whole, the experts are saying that export default is a bad thing and that you shouldn't mix modules and namespaces.
You can do it, if you feel you must. here is the example with a namespace / default:
namespace Component {
export class Base {
}
export class A {
doStuffA() {}
}
export class B {
doStuffB() {}
}
export class C extends Base {
}
}
export default Component;
And the use:
import Component from './component';
const a = new Component.A();

Categories