How do I split a TypeScript class into multiple files?

JavascriptTypescript

Javascript Problem Overview


I found a lot of examples and also tried myself to split a module into several files. So I get that one, very handy. But it's also practical sometimes to split a class for the same reason. Say I have a couple of methods and I don't want to cram everything into one long file.

I'm looking for something similar to the partial declaration in C#.

Javascript Solutions


Solution 1 - Javascript

Lately I use this pattern:

// file class.ts
import { getValue, setValue } from "./methods";

class BigClass {
    public getValue = getValue;
    public setValue = setValue;

    protected value = "a-value";
}
// file methods.ts
import { BigClass } from "./class";

function getValue(this: BigClass) {
    return this.value;
}

function setValue(this: BigClass, value: string ) {
   this.value = value;
}

This way we can put methods in a seperate file. Now there is some circular dependency thing going on here. The file class.ts imports from methods.ts and methods.ts imports from class.ts. This may seem scary, but this is not a problem. As long as the code execution is not circular everything is fine and in this case the methods.ts file is not executing any code from the class.ts file. NP!

You could also use it with a generic class like this:

class BigClass<T> {
    public getValue = getValue;
    public setValue = setValue;

    protected value?: T;
}

function getValue<T>(this: BigClass<T>) {
    return this.value;
}

function setValue<T>(this: BigClass<T>, value: T) {
    this.value = value;
}

Solution 2 - Javascript

You can't.

There was a feature request to implement partial classes, first on CodePlex and later on GitHub, but on 2017-04-04 it was declared out-of-scope. A number of reasons are given, the main takeaway seems to be that they want to avoid deviating from ES6 as much as possible:

> TypeScript already has too many TS-specific class features [...] Adding yet another TS-specific class feature is another straw on the camel's back that we should avoid if we can. [...] So if there's some scenario that really knocks it out of the park for adding partial classes, then that scenario ought to be able to justify itself through the TC39 process.

Solution 3 - Javascript

I use this (works in typescript 2.2.2):

class BigClass implements BigClassPartOne, BigClassPartTwo {
    // only public members are accessible in the
    // class parts!
    constructor(public secret: string) { }

    // this is ugly-ish, but it works!
    methodOne = BigClassPartOne.prototype.methodOne;
    methodTwo = BigClassPartTwo.prototype.methodTwo;
}

class BigClassPartOne {
    methodOne(this: BigClass) {
        return this.methodTwo();
    }
}

class BigClassPartTwo {
    methodTwo(this: BigClass) {
        return this.secret;
    }
}

Solution 4 - Javascript

I use plain subclassing when converting large old multi file javascript classes which use 'prototype' into multiple typescript files:

bigclassbase.ts:

class BigClassBase {
    methodOne() {
        return 1;
    }

}
export { BigClassBase }

bigclass.ts:

import { BigClassBase } from './bigclassbase'

class BigClass extends BigClassBase {
    methodTwo() {
        return 2;
    }
}

You can import BigClass in any other typescript file.

Solution 5 - Javascript

You can use multi file namespaces.

Validation.ts:

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}

LettersOnlyValidator.ts (uses the StringValidator from above):

/// <reference path="Validation.ts" /> 
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

Test.ts (uses both StringValidator and LettersOnlyValidator from above):

/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />

// Some samples to try
let strings = ["Hello", "101"];

// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["Letters only"] = new Validation.LettersOnlyValidator();

Solution 6 - Javascript

A modified version of proposed pattern.

// temp.ts contents
import {getValue, setValue} from "./temp2";

export class BigClass {
    // @ts-ignore - to ignore TS2564: Property 'getValue' has no initializer and is not definitely assigned in the constructor.
    public getValue:typeof getValue;

    // @ts-ignore - to ignore TS2564: Property 'setValue' has no initializer and is not definitely assigned in the constructor.
    public setValue:typeof setValue;
    protected value = "a-value";
}

BigClass.prototype.getValue = getValue;
BigClass.prototype.setValue = setValue;

//======================================================================
// temp2.ts contents
import { BigClass } from "./temp";

export function getValue(this: BigClass) {
    return this.value;
}

export function setValue(this: BigClass, value: string ) {
    this.value = value;
}

Pros

  • Doesn't create additional fields in class instances so there is no overhead: in construction, destruction, no additional memory used. Field declations in typescript are only used for typings here, they don't create fields in Javascript runtime.
  • Intellisence is OK (tested in Webstorm)

Cons

  • ts-ignore is needed
  • Uglier syntax than @Elmer's answer

The rest properties of solutions are same.

Solution 7 - Javascript

Why not just use Function.call that js already comes with.

class-a.ts

Class ClassA {
  bitten: false;

  constructor() {
    console.log("Bitten: ", this.bitten);
  }

  biteMe = () => biteMe.call(this);
}

and in other file bite-me.ts

export function biteMe(this: ClassA) {
  // do some stuff
  // here this refers to ClassA.prototype

  this.bitten = true;

  console.log("Bitten: ", this.bitten);
}

// using it

const state = new ClassA();
// Bitten: false

state.biteMe();
// Bitten: true

For more information have a look at the definition of Function.call

Solution 8 - Javascript

Personally I use @partial decorator acts as a simplified syntax that may help divide functionality of a single class into multiple  class files ... https://github.com/mustafah/partials

Solution 9 - Javascript

Modules let you extend a typescript class from another file:

user.ts

export class User {
  name: string;
}

import './user-talk';

user-talk.ts

import { User } from './user';

class UserTalk {
  talk (this:User) {
    console.log(`${this.name} says relax`);
  }
}

User.prototype.sayHi = UserTalk.prototype.sayHi;

declare module './user' {
  interface User extends UserTalk { }
}

Usage:

import { User } from './user';

const u = new User();
u.name = 'Frankie';
u.talk();
> Frankie says relax

If you have a lot of methods, you might try this:

// user.ts
export class User {
  static extend (cls:any) {
    for (const key of Object.getOwnPropertyNames(cls.prototype)) {
      if (key !== 'constructor') {
        this.prototype[key] = cls.prototype[key];
      }
    }
  }
  ...
}

// user-talk.ts
...
User.extend(UserTalk);

Or add the subclass to the prototype chain:

...
static extend (cls:any) {
  let prototype:any = this;
  while (true) {
    const next = prototype.prototype.__proto__;
    if (next === Object.prototype) break;
    prototype = next;
  }
  prototype.prototype.__proto__ = cls.prototype;
}

Solution 10 - Javascript

We can extend class methods gradually with prototype and Interface definition:

import login from './login';
import staffMe from './staff-me';

interface StaffAPI {
  login(this: StaffAPI, args: LoginArgs): Promise<boolean>;
  staffsMe(this: StaffAPI): Promise<StaffsMeResponse>;
}

class StaffAPI {
  // class body
}

StaffAPI.prototype.login = login;
StaffAPI.prototype.staffsMe = staffsMe;

export default StaffAPI;

Solution 11 - Javascript

This is how i have been doing it Mixins approach

Solution 12 - Javascript

To add to @Elmer's solution, I added following to get it to work in separate file.

some-function-service-helper.ts

import { SomeFunctionService } from "./some-function-service";

export function calculateValue1(this: SomeFunctionService) {
...
}

some-function-service.ts

import * as helper from './some-function-service-helper';

@Injectable({
    providedIn: 'root'
})
export class SomeFunctionService {

    calculateValue1 = helper.calculateValue1;  //  helper function delcaration used for getNewItem

    public getNewItem() {
        var one = this.calculateValue1();
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAdrian RoscaView Question on Stackoverflow
Solution 1 - JavascriptElmerView Answer on Stackoverflow
Solution 2 - Javascriptuser247702View Answer on Stackoverflow
Solution 3 - JavascriptElmerView Answer on Stackoverflow
Solution 4 - JavascriptmaspView Answer on Stackoverflow
Solution 5 - JavascriptHypenateView Answer on Stackoverflow
Solution 6 - JavascriptYuri YaryshevView Answer on Stackoverflow
Solution 7 - JavascriptRushi patelView Answer on Stackoverflow
Solution 8 - JavascriptMustafahView Answer on Stackoverflow
Solution 9 - JavascriptbendytreeView Answer on Stackoverflow
Solution 10 - JavascriptMasih JahangiriView Answer on Stackoverflow
Solution 11 - JavascriptPranay DuttaView Answer on Stackoverflow
Solution 12 - JavascriptSamJackSonView Answer on Stackoverflow