Use of TypeScript

The code examples in this guide are written in TypeScript. But is it necessary to use TypeScript when you want to write React or Angular applications? And how well is TypeScript supported?

React is based on JavaScript and does not even need a compilation step. But using TypeScript is well supported: The TypeScript compiler comes with JSX support and type definitions are available for React.

Angular is written in TypeScript. And it even comes with it's own compiler and syntax on top of that. While it is theoretically possible to use Angular with plain JavaScript, this is not something you would usually want to do.

TypeScript Support

As you have already seen, you can use TypeScript with both React and Angular. But while Angular is based on TypeScript and uses some TypeScript features (like classes, decorators, etc.) as part of the framework, React is written in JavaScript, and you are free to use TypeScript however you want—or not at all.

TypeScript Support in React

React itself is written in JavaScript. And you can, in fact, write a React application in pure JavaScript—at least when you do not use JSX:

export function JsComponent(props) {
  const icon = React.createElement('img', { src: props.icon })
  const iconDiv = React.createElement('div', null, icon)
  const textDiv = React.createElement('div', null, props.text)
  console.log(React.createElement('div', null, iconDiv, textDiv))
  return React.createElement('div', null, iconDiv, textDiv)
}
export function JsxComponent(props) {
  return <div>
    <div><img src={props.icon} /></div>
    <div>{props.text}</div>
  </div>
}

The two components above are equal: JSX will compile the HTML-like syntax down to React.createElement function calls.

So, you can use plain JavaScript to write React code, and then you do not even need a compilation step. Everything you add on top—TypeScript and even JSX—is just syntactic sugar.

But that does not mean that it is useless. Just like JSX can make your components easier to read by removing some boilerplate, TypeScript, too, can make your code more readable and help you avoid some common defects.

Since TypeScript is a drop-in replacement for JavaScript, and since React is plain JavaScript, you can use all the TypeScript features you want, anywhere in your code. React supports TypeScript very well because it does not add anything special to plain JavaScript.

In React, I do not use classes or enums a lot. They are useful in some cases, but in most situations, simple interfaces, type aliases and union types are sufficient:

type Status = 'draft' | 'sending' | 'sent' | 'published'

type Posting = {
  status: Status,
  text: string,
}

I could also have used interface Posting instead of type Posting—in most cases, the result is the same, but there is a subtle difference.

Types like that are very useful for your models and also for use within your react components, e.g., for the props of your component. You can assign default values to optional fields directly when you destructure the props:

type PostingEditorProps = {
  posting: Posting,
  isReply?: boolean,
}
function Posting({posting, isReply = false}: PostingEditorProps) {
  // ...
}

Make sure to enable strict mode in your tsconfig.json by adding the line "strict": true,. This enables many useful features that can help you debug problems and avoid defects, like strict null checks and exhaustive switch checking:

function getSendingStatusLabel(status: Status): string {
    switch(status) {
        case 'draft': return 'Not sent'
        case 'sending': return 'Sending...'
        //When you comment out the following case for
        //'sent', the TypeScript compiler will report an error
        //because the function does not always return a
        //string:
        case 'sent': return 'Sent, waiting for publishing...'
        case 'published': return 'Your post is online!'
        //No default case necessary: The compiler will
        //report an error if we forget a case!
    }
}

You also need type definitions for React itself. Fortunately, there is a community effort to create those at DefinitelyTypes: @types/react and @types/react-dom. If you created your React application with create-react-app, the tool has automatically added those dependencies to your project, so you are already good to go.

TypeScript Support in Angular

Since Angular itself is based on TypeScript, it also supports TypeScript out of the box. But for the same reason, Angular is also more opinionated than React in how you should use TypeScript.

When you create new components, modules or services, you have to write TypeScript classes.

And you must add metadata to those classes using decorators—an experimental feature of TypeScript that will eventually become a part of the language. But for now, there is a note in the documentation saying that it might change in the future.

Let's have a look at the source code of a newly created component.

@Component({
  selector: 'app-new-component',
  templateUrl: './new-component.component.html',
  styleUrls: ['./new-component.component.css']
})
export class NewComponentComponent implements OnInit {
  constructor() { }

  ngOnInit(): void {
  }
}

I ran npm run ng g component NewComponent and the Angular CLI created this component, NewComponentComponent, for me (the repetition in the name is there because the Angular CLI appends Component to the name I gave it).

The class implements OnInit, and because of that, there must be a function ngOnInit(): void in this class. Angular will call this function when it initializes the component. Angular will even call a function with exactly that name if you do not add the implements OnInit (and other lifecycle functions and their interfaces).

So, implementing the interface is optional for Angular itself. But if you do not add the implemented interfaces, you will lose some type-system-level safety in your own code.

The class also has a constructor, which is empty for now. You can add the dependencies of your class—"services" in Angular nomenclature—here: constructor(private someService: SomeService) { }. This code creates a private field someService on the class that will be initialized in the constructor. And this code instructs Angular to inject an instance of the class SomeService into the component automatically.

The decorator @Component(...) adds metadata to the component class—in this case, the component's selector, template and style. Angular uses this metadata in addition to the code in the class itself when creating the component.

When you add public fields to your component, you can reference them from the component's template. And Angular will also type-check the use in the template:

export class NewComponentComponent implements OnInit {
  val1: string='value'
  val2?: string
  
  //...
}
<p>{{val1}} has {{val1.length}} chars</p>
<!-- error TS2532: Object is possibly undefined -->
<p>{{val2}} has {{val2.length}} chars</p>

The Angular compiler will report the error TS2532: Object is possibly undefined for the code that tries to access val2.length in the template since val2 is optional and might not have a value at this point in time.

To get strict null checks, exhaustive switch checks and other useful TypeScript features, you should also enable strict mode in your tsconfig.json by adding the line "strict": true,.

Special Syntax

When you write a React application, you usually use JSX to create React Elements. Apart from that, there is no special syntax to consider. Angular, on the other hand, comes with its own compiler (ivy in newer versions) that adds some Angular-specific syntax on top.

Special Syntax in React

Most React projects also use JSX since it makes your components easier to read. So, when you write React in plain JavaScript, you need an extra compilation step that transforms your JSX code to React.createElement calls.

But when you use TypeScript, you do not need that: TypeScript supports JSX out of the box. You still have only one compilation step, which is the TypeScript compiler itself. Use the extension .tsx for all files that contain JSX code, and you are good to go.

When you look at the tsconfig.json file of your generated project, you will see that it contains an entry for "jsx":

{
  "compilerOptions": {
    ...
    "jsx": "react"
  },
  ...
}

This tells the TypeScript compiler to transform JSX elements into calls to React.createElement. There are more options to configure how exactly TypeScript will transform JSX code. But for a plain React project, the one line above is all you need.

Special Syntax in Angular

In some parts of your templates, Angular uses a special syntax that looks like TypeScript but does not provide the full feature set. And sometimes there are even some differences.

Look at this code from the last example that uses interpolation to access a value from the component:

<p>{{val1}} has {{val1.length}} chars</p>

The values inside the double curly brackets look like "normal" TypeScript code. But there are some restrictions: You cannot use assignments or create new objects and chain expressions. On the other hand, there are some new "template expression operators" that do not exist in TypeScript at all.

To get a full list of the restrictions and new features in template expressions, head over to the documentation.

In structural directives like "ngFor", you must use a special microsyntax. Look at this code example, which comes directly from the Angular documentation:

<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

Some parts inside the string after *ngFor* look like TypeScript, but the code also does not really look like TypeScript as a whole. The microsyntax you must use in structural directives has its own rules and features.

let defines a template input variable. of and trackBy are features of ngFor itself. So, the microsyntax for other structural directives—like ngIf—can look slightly different.

Head over to the documentation of each structural directive to learn the exact microsyntax you can use.

To Recap...

Since React is based on pure JavaScript, you can use TypeScript anywhere you want and however you want. There are type definitions for both React and ReactDOM—and TypeScript itself supports JSX, so you do not need an extra JSX compiler.

Even though I will use TypeScript in the React example code from now on, I will not write about TypeScript itself very much. If you specifically want to learn more about how to use TypeScript with React, Stefan Baumgartner has written a very interesting series of posts on his blog.

Angular itself is written in TypeScript, so it supports TypeScript out of the box (it is theoretically possible to write an Angular application in plain JavaScript, but I would not recommend it). But it is also more opinionated on how you should use TypeScript, making extensive use of classes and the experimental decorators feature.

And Angular uses special syntax in some places—syntax that looks somewhat like TypeScript but is subtly different. Even though you can often write code as if it was "normal" TypeScript in those places, you should be aware of the fact that it is not.