Thinking About Static vs. Private Methods In TypeScript / Angular 2

Technically speaking, there's really nothing different about TypeScript. Meaning, it all transpiles down to the same ES5 that we can write today. But, the fact that TypeScript hides certain complexities means that we are more likely to think about the code from a different perspective. Such is the case with static methods. Now that we can seamlessly mix static methods into our class definitions, it's certainly worth taking another look at when we should be using static methods in our TypeScript / Angular 2 classes.

Run this demo in my JavaScript Demos project on GitHub.

One of the JavaScript features that TypeScript facilitates (through decreased complexity) is class extension. And, now that sub-classing is easier, it's more important that we think about our sub-class / super-class relationships; and, specifically about how the structure of our classes affects inheritance.

In the book Fundamentals Of Object-Oriented Design, Meilir Page-Jones states that a sub-class should never access the private methods of its super-class. The private methods of any class are, just that, private. And, a sub-class shouldn't know about anything more than the public interface already exposed by the super-class.

On a loosely related note, Sandi Metz once talked about a philosophy in which a class shouldn't have private methods at all - that any private methods should be factored out into their own set of classes with cohesive logic and functionality. At first, this sounded very suspect to me; but, the more I've noodled on it, the more it actually makes sense (at least to a good degree).

Bringing this back around to TypeScript, these preceding concepts make me think that I should, when possible, err on the side of pure, static methods over private methods. Not only does this make the methods easier to reason about (since they depend solely on their inputs); but, it means that the functionality can be more easily reused and leveraged by other classes. It also means that when I extend a super-class, I don't have to make assumptions about the private implementation of that super-class.

Now, while static methods can be seamlessly mixed in with the class definition, it doesn't mean that they can be referenced off of the "this" context. Unlike public and private methods, static methods are only available on the Class itself, not on the instances of the class.

To demonstrate this, I've put together a simple demo in which we take the input value from a form control and reverse it. The logic that performs the reversal is provided by a static method on the component, which we consume from within the public methods.

// Import the core angular services. import { Component } from "@angular/core"; @Component({ selector: "my-app", template: ` <input [(ngModel)]="input" (ngModelChange)="handleModelChange()" /> <strong>Reversed:</strong> <span class="reversed">{{ reversedInput }}</span> ` }) export class AppComponent { // I hold the input value (for the ngModel). public input: string; // I hold the value that is a reversed version of the input. public reversedInput: string; // I initialize the component. constructor() { this.input = ""; this.reversedInput = ""; } // --- // PUBLIC METHODS. // --- // I handle changes in the input, calculating the reversed value. public handleModelChange() : void { // Here, we are using a PRIVATE method to determine some of the businessy logic; // but, we're using a STATIC method to implement some of the more generic logic. // This makes the static functionality available to any class that might want // to sub-class the AppComponent (since private methods really shouldn't be // referenced from a sub-class if it can be avoided, as it increases coupling // between the sub-class and the super-class). // -- // NOTE: The STATIC methods could be moved into various utility classes if we // wanted to split them out. this.reversedInput = this.isReversible( this.input ) ? AppComponent.reverseString( this.input ) : this.input ; } // --- // PRIVATE METHODS. // --- // I determine if the given string is long enough to be meaningful in reverse. private isReversible( value: string ) : boolean { return( value.length > 1 ); } // --- // STATIC METHODS. // NOTE: Static methods are available off the Class, not the Instance. // --- // I reverse the given string value. static reverseString( value: string ) : string { return( value.split( "" ).reverse().join( "" ) ); } }

As you can see, the reverseString() method is static. This means that we have to access it as a method on AppComponent instead of "this". But, this works perfectly well. And, when we run the above code, we get the following output:

To be clear, I am not advocating that we stop using private methods. There are many cases where private methods make the most sense - helping to break down the complexity of internal algorithms. But, I think that there are also many cases were private methods are private simply because they are not part of the "API" of an object. In those cases, I think that those private methods would be best served as static methods on the class; or, factored out of the class completely. This way, that non-instance-specific logic can be leveraged effectively by sub-classes; or, simply by classes that are trying to implement similar yet divergent logic.

Of course, when one class depends on another class, no matter what the mechanism, the relationship increases complexity and coupling within the application. So, all things done in measure.

Tweet This Groovy post by @BenNadel - Thinking About Static vs. Private Methods In TypeScript / Angular 2 Woot woot — you rock the party that rocks the body!







