cancel
Showing results for 
Search instead for 
Did you mean: 
Reply
Dlabar
MVP

Splitting Up A Large TypeScript Class

Let's say I've extended the FormContext by creating a new TS class that wraps it, and adds helper functions.  This new ExtendedContext has functions like getDisplayValue(attName) which gets the attribute, handles it not being on the form, determines the attribute type, and appropriately returns what the "displayed value" is.  As I add more helper functions, the class get's bigger and bigger, which means I need to start splitting the class into more files, but I don't want the API to change. The consuming code should not have to know that it needs to create a DisplayExtendedContext class to call getDisplayValue, all functions should exist on the main extended context. What's the recommended approach?

 

My current approach feels wrong and looks like this:

 

// extendedContex.ts
import { DisplayContext } from "../context/display";

export class ExtendedContext implements XrmExt.ExtendedContext {
  public context: Xrm.FormContext // Actual Xrm.Client form context
  private display: DisplayContext;
  
  constructor(context: Xrm.FormContext){
	this.context = context;
	this.display = new DisplayContext(this);
  }
  
  public getDisplayValue(att: string): string {
	return display.getDisplayValue(att);
  }
}

// xrmExt.d.ts
declare namespace XrmExt {
	interface ExtendedContext {
		getDisplayValue(att: string): string;
	}
}

// ../context/display.ts
export class DisplayContext {
	private context: XrmExt.ExtendedContext;
	
	constructor(context: XrmExt.ExtendedContext){
		this.context = context;
	}
	
	public getDisplayValue(att: string): string {
		// Do logic here, with full access to the ExtendedContext
	}
}

 Here are the issues it has:

  1. I have to duplicate the pass throughs for ExtendedContext functions. So every function I add, I have to implement it in the smaller context class, and then add it as a pass through in the ExtendedContext class and to the ExtendedContext interface.  I'm lazy, I don't want to have to do that for every function.
  2. This one is more minor, but the ExtendedContext that is passed to the DisplayContext is not fully initialized, which could lead to null ref errors.  For example, if DisplayContext were to call a function on the XrmExt.ExtendedContext interface in it's constructor that it itself implements, the class level "display" field of the ExtendedContext class will not be populated, and a null ref exception would be thrown.  An "unspoken" rule of never access the ExendedContext from the constructor of one of the smaller classes would prevent this from ever being an issue.

I'm guessing Mixings might be the way forward here, but I'm just not sure.  Thoughts / Suggestions?

1 ACCEPTED SOLUTION

Accepted Solutions
cchannon
Memorable Member
Memorable Member

OK, this is a complex one, for sure, but I am not sure it is as complex as you think.

 

Inheritance between classes in TS does not intrinsically mean that you must redeclare every member at every level. Maybe I am missing something you're trying to do here, but why do you think you need to redeclare every member? Try the below super-simple example in a raw TS file: 

export class myclass{
    public aString: string;
    public aMethod: (() => void)
}

export class myOtherClass extends myclass{
    public aNumber: number;
    constructor(aNumber: number, aString: string) {
        super();
        this.aNumber = aNumber;
        this.aString = aString;
    }
}

let thing : myOtherClass = new myOtherClass(1,"thing");
thing.aMethod();

You'll see that even though myClass implements aMethod and myOtherClass does not, you can still invoke it on the class object instantiated as myOtherClass because myOtherClass extends myClass. And, even though myOtherClass does not have an explicitly declared member "aString" it can still use it in its constructor.

 

This transitive property of class members extends as far as you would like your inheritance to go, so even if a child member is of a class 47 classes deep, it is still accessible, still usable in constructors and other contexts, and still extensible if you want to invert your model to be subclasses instead of a parent class construction, even if nowhere along the line it was explicitly re-declared.

 

View solution in original post

2 REPLIES 2
cchannon
Memorable Member
Memorable Member

OK, this is a complex one, for sure, but I am not sure it is as complex as you think.

 

Inheritance between classes in TS does not intrinsically mean that you must redeclare every member at every level. Maybe I am missing something you're trying to do here, but why do you think you need to redeclare every member? Try the below super-simple example in a raw TS file: 

export class myclass{
    public aString: string;
    public aMethod: (() => void)
}

export class myOtherClass extends myclass{
    public aNumber: number;
    constructor(aNumber: number, aString: string) {
        super();
        this.aNumber = aNumber;
        this.aString = aString;
    }
}

let thing : myOtherClass = new myOtherClass(1,"thing");
thing.aMethod();

You'll see that even though myClass implements aMethod and myOtherClass does not, you can still invoke it on the class object instantiated as myOtherClass because myOtherClass extends myClass. And, even though myOtherClass does not have an explicitly declared member "aString" it can still use it in its constructor.

 

This transitive property of class members extends as far as you would like your inheritance to go, so even if a child member is of a class 47 classes deep, it is still accessible, still usable in constructors and other contexts, and still extensible if you want to invert your model to be subclasses instead of a parent class construction, even if nowhere along the line it was explicitly re-declared.

 

View solution in original post

I was hoping to avoid chaining all of the inherited classes together, but this ended up being the approach I went with.  Now I just have to maintain the correct order of inheritance of all the classes and make sure there are no circular references.  One of the few times that I wish TypeScript had a C# feature (partial classes).

Helpful resources

Announcements
PA_User Group Leader_768x460.jpg

Manage your user group events

Check out the News & Announcements to learn more.

Power Query PA Forum 768x460.png

Check it out!

Did you know that you can visit the Power Query Forum in Power BI and now Power Apps

Carousel 2021 Release Wave 2 Plan 768x460.jpg

2021 Release Wave 2 Plan

Power Platform release plan for the 2021 release wave 2 describes all new features releasing from October 2021 through March 2022.

R2 (Green) 768 x 460px.png

Microsoft Dynamics 365 & Power Platform User Professionals

DynamicsCon is a FREE, 4 half-day virtual learning experience for 11,000+ Microsoft Business Application users and professionals.

Users online (1,250)