Is it possible for an object's property specified in a TypeScript interface to also incorporate the interface within its own declaration?

While it may seem like an unusual request, in my specific scenario, it would be a perfect fit.

I am working with an object named layer that looks something like this:

const layer = {
    Title: 'parent title',
    Name: 'parent name',
    Layer: {
        Title: 'child title'
    }
}

This object has a mandatory property - Title, and optional properties - Name and Layer.

If the Layer property exists, it follows the same structure as described above (it could have additional nested layers).

Now, I need to create an interface for this object, and I'm considering using the following structure:

interface LayerInterface {
    Title: string;
    Name?: string;
    Layer?: LayerInterface;
}

My question is whether I can use LayerInterface as the type of the Layer property which is defined within the LayerInterface itself.

I'm simply wondering if this approach is valid or if there's a simpler solution available.

Answer №1

Absolutely, your recursive interface definition seems to be functioning as intended:

interface LayerInterface {
    Title: string;
    Name?: string;
    Layer?: LayerInterface;
}

The code compiles without any errors, and you can clearly see how it enforces specific types for nested properties within the object.

function processLayer(layer: LayerInterface) {}

processLayer(layer); // works fine

const problematicLayer = { Title: "", Name: "", Layer: { Title: 123, Name: false } }
processLayer(problematicLayer); // encounters an error
// --------> ~~~~~~~~
/* Argument of type
  '{ Title: string; Name: string; Layer: { Title: number; Name: boolean; }; }' 
  is not assignable to parameter of type 'LayerInterface'. 
*/

In this scenario, the incorrect data types in the nested Title and Name properties cause the problematicLayer object to fail the type check against LayerInterface.


This approach is not unusual at all; many interfaces and classes commonly utilize this recursive structure. For instance, tree-like structures like the DOM often have similar type definitions that reference themselves internally.

For example, in the DOM, each Element node has a children property which holds an array-like collection of Element nodes, giving you the ability to write recursive element-processing functions seamlessly:

function processElement(elem: Element) {
    console.log(elem.nodeName);
    for (let i = 0; i < elem.children.length; i++) {
        processElement(elem.children[i]);
    }
}

Regarding documentation:

Formal documentation regarding this aspect of TypeScript can be found in the (somewhat outdated) TypeScript Spec:

Classes and interfaces are capable of self-referencing in their internal structure, thus generating recursive types with infinite nesting. A simple illustration is shown by the following type:

interface A { next: A; }

which reflects an infinitely embedded sequence of 'next' elements.

A similar concept applies to type aliases, as illustrated in the relevant section of the TypeScript handbook:

The ability to refer back to itself via a property is also applicable to type aliases:

type Tree<T> = {
   value: T;
   left: Tree<T>;
   right: Tree<T>;
}

Hopefully, this information proves helpful; best of luck with your coding endeavors!

Link to code on playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting: Angular add/edit form issue with retrieving data from a Span element within an ngFor loop

I am currently working on an add/edit screen that requires submitting a list, among other data. The user will need to check 2-3 checkboxes for this specific data, and the saved record will have multiple options mapped. Here is what the HTML looks like: &l ...

Issue with the Mat paginator items per page functionality not functioning properly in Angular 9

I have encountered an issue where changing the items displayed per page on a table I'm rendering from an observable is not functioning as expected. Despite trying methods such as ngAfterViewInit and calling page events, I did not see any changes. ...

Is there a Typescript function error that occurs when attempting to rename a key, stating it "could potentially be instantiated with a different subtype"?

I am currently working on a Mongoify type that accepts a type T, removes the id key, and replaces it with an _id key. type Mongoify<T extends {id: string}> = Omit<T, "id"> & { _id: ObjectId }; function fromMongo<T extends ...

Navigating through complex routes was tricky in Next.js 14 due to the error 404 on hard navigation with

While working on implementing tab groups using Parallel Routes in Next.js 14, I encountered an issue where a 404 page only appears during hard navigation to /gnb/mypage/tab1. The tabs and their navigation function properly on initial render and when switch ...

Leveraging the power of react-hook-form in combination with the latest version 6 of @mui

When using MUI v5 date pickers, I utilized the following method to register the input with react-hook-form: <DatePicker ...date picker props renderInput={(params) => { return ( <TextField {...params} ...

What does the concept of "signaling intent" truly signify in relation to TypeScript's read-only properties?

Currently diving into the chapter on objects in the TypeScript Handbook. The handbook highlights the significance of managing expectations when using the readonly properties. Here's a key excerpt: It’s crucial to clarify what readonly truly signif ...

Setting up only two input fields side by side in an Angular Form

I'm fairly new to Angular development and currently working on creating a registration form. I need the form to have two columns in a row, with fields like Firstname and Lastname in one row, followed by Phone, Email, Password, and Confirm Password in ...

Steps to develop a sub-route specifically for a single word

Take a look at this code: {path : 'recipes', component:RecipesComponent, children:[ {path:':id', component:RecipeDetailComponent}, {path:':new', component:NewRecipeComponent } ]}, No matter which link you use: h ...

Tips for parsing through extensive JSON documents containing diverse data types

In the process of developing an npm package that reads json files and validates their content against predefined json-schemas, I encountered issues when handling larger file sizes (50MB+). When attempting to parse these large files, I faced memory allocati ...

Update the class attributes to a JSON string encoding the new values

I have created a new class with the following properties: ''' import { Deserializable } from '../deserializable'; export class Outdoor implements Deserializable { ActualTemp: number; TargetTemp: number; Day: number; ...

The function this.listCatModel.push is not a valid operation

I have a dropdown that I want to populate using the following code: this.categoryService.GetListItem(this.GetAllcatListUrl).subscribe(data=>{ this.listCatModel=data, this.listCatModel.push({ id:0, name:'Main Category', parent ...

What are some ways to optimize the performance of a Select Box?

I am attempting to show a lengthy list of countries in an ion-select. Currently, there are 249 countries that I need to load. Unfortunately, the rendering performance is quite slow on my phone. <ion-list margin-top margin-bottom> <ion-item> ...

Is it possible to customize the color of the placeholder and clear-icon in the ion-search bar without affecting

I am working with two ion-search bars and I need to customize the placeholder and clear icon color for just one of them. <ion-searchbar class="search-bar" placeholder="search"></ion-searchbar> My goal is to target a specific i ...

When attempting to access Firebase Storage with Angular, you may encounter the error message: "TypeError: app.storage

Having trouble connecting my Angular app to FireBase. The component appears blank and the Chrome console is showing a 'TypeError: app.storage is not a function'. Any ideas on what I might be doing wrong? Thanks in advance. ng --version Angular C ...

Ways to employ data binding for extracting a user-input value and performing multiplication operations with the enclosed {{ ...}} tags

My API response includes the price of a product, which is represented as {{price}} I have a system where I can add or reduce the number of products: <div class="number-input"> <h2>Price: {{price }}</h2> <button oncli ...

invoke a specified function at runtime

I recently came across a useful library called https://github.com/ivanhofer/typesafe-i18n This library has the capability to generate strongly typed translation data and functions, as illustrated below. (the examples provided are simplified for clarity) e ...

Using private members to create getter and setter in TypeScript

Recently, I developed a unique auto getter and setter in JavaScript which you can view here. However, I am currently unsure of how to implement this functionality in TypeScript. I am interested in creating an Object Oriented version of this feature if it ...

When conducting a test, it was found that the JSX element labeled as 'AddIcon' does not possess any construct or call signatures

Here is a code snippet I'm currently working with: const tableIcons: Icons = { Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />), Check: forwardRef((props, ref) => <Check {...props} ref={ref} />) }; const AddIcon ...

Is there a way for me to steer clear of having to rely on the Elvis Operator?

Throughout my journey building my Angular 2 website, I've found the Elvis Operator to be a crucial element that makes everything possible. It seems like every task I undertake involves mastering how to apply it correctly in every instance where data i ...

What is the best way to call a method within a TypeScript class using its name as a string while already inside the class itself?

Currently, I am developing a class that automates the creation of routes for Express and invokes a function in a controller. However, upon trying to execute this[methodName](req, res), I come across an error message stating: 'Element implicitly has an ...