Is there a distinction in Typescript between the return types of Object.seal and .freeze?

I am looking to safeguard the constant object content from any changes during runtime. This includes alterations to both the object structure and its content. The preferred method for achieving this is by using Object.freeze.

interface iRO<T> {
    readonly [key: string]: T | null;
}

const nc: iRO<HTMLElement> = {
    main: document.querySelector("body"),
    aside: document.querySelector("body > aside"),
}

const go: iRO<HTMLElement> = Object.seal({
    main: document.querySelector("body"),
    aside: document.querySelector("body > aside"),
})

const nogo: iRO<HTMLElement> = Object.freeze({
    main: document.querySelector("body"),
    aside: document.querySelector("body > aside"),
})

To exemplify, I have created three constants with identical content. The compilation succeeds with the constants "nc" and "go". However, the compiler raises an error with "nogo":

»Type 'Readonly<{ main: HTMLBodyElement | null; aside: Element | null; }>' is not assignable to type 'iRO'. Property 'aside' is incompatible with index signature. Type 'Element | null' is not assignable to type 'HTMLElement | null'. Type 'Element' is missing properties such as accessKey, accessKeyLabel, autocapitalize, dir, and more.«

This behavior is perplexing. Shouldn't Object.freeze have the same impact on the assigned type as Object.seal?

Explore the issue further in the Typescript Playground

Answer №1

The issue arises from how TypeScript determines the type of the object prior to being assigned to the variable on the left-hand side.

When you specify:

const nc: iRO<HTMLElement> = {

This indicates to TypeScript that the object on the right must adhere to iRO<HTMLElement>. In other words, its values must be of type HTMLElement | null. This works for nc because

document.querySelector("body")
with the correct type argument can provide this type. One way to understand the initial version is:

const nc: iRO<HTMLElement> = {
    main: document.querySelector<HTMLElement>("body"),
    aside: document.querySelector<HTMLElement>("body > aside"),
}

This approach is also compatible with Object.seal since Object.seal doesn't alter the type of the argument. Its type definition is:

seal<T>(o: T): T;

However, Object.freeze functions differently. Its type definition is:

freeze<T>(o: T): Readonly<T>;

As it produces a new type, the T cannot change to satisfy generics. The type of the object passed in must remain static. It can be visualized as:

const obj = {
    main: document.querySelector("body"),
    aside: document.querySelector("body > aside"),
};
const nogo: iRO<HTMLElement> = Object.freeze(obj);

However, the parameter has a type of

const obj: {
    main: HTMLBodyElement | null;
    aside: Element | null;
}

and the resultant type returned by Object.freeze is

Readonly<{
    main: HTMLBodyElement | null;
    aside: Element | null;
}>

This creates a new type - and an Element cannot be assigned to an HTMLElement.

If Object.seal yielded a new type, rather than the same type as the argument, the same issue would arise with it as well. However, since Object.seal maintains the same type as its argument, this problem doesn't occur.

To rectify this, utilize the generic parameter for querySelector:

const nc = Object.freeze({
    main: document.querySelector("body"),
    aside: document.querySelector<HTMLElement>("body > aside"),
})

Answer №2

It is my understanding that when you call

document.querySelector("body")
, it returns an instance of HTMLBodyElement, an extension of HTMLElement.
However, when you call
document.querySelector("body > aside")
, it does not return an HTMLElement but rather an Element, making it a subinterface of HTMLElement.

Therefore, expecting all keys to at least extend from HTMLElement when using iRO<HTMLElement> is reasonable. When you use a normal object or Object.seal, you are performing a smart type cast. On the other hand, using Object.freeze will freeze your object and determine that your aside will only be of type Element, not HTMLElement.

However, there is a workaround:

const nogo: Readonly<iRO<HTMLElement>> = Object.freeze({
    main: document.querySelector("body"),
    aside: document.querySelector("body > aside") as HTMLElement | null,
})

Edit:

After considering the response from @CertainPerformance, it is advisable to use

document.querySelector<HTMLElement>("body > aside")
instead of
document.querySelector("body > aside") as HTMLElement | null

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

In Angular 4, you can easily preselect multiple options in a mat-select dropdown by passing an

Seeking assistance with setting the options of a mat-select in Angular 4. The issue at hand is as follows: 1. Initially, there are two variables: options and checkedOptions options: string[]; checkedOptions: string[] //Retrieved from the database; 2. T ...

Exploring the potential of Vue with Router to create a dynamic multi-page

Struggling to come up with a suitable title for this Vue project dilemma. Here's what I'm facing: Recently started using Router in my Vue project and feeling quite lost. The setup in App.vue simply includes <RouterView>, which seems stra ...

The dimensions of the d3 div remain constant despite any modifications to its attributes

In my angular application, I am trying to customize the width and height of div elements in d3 when I select a legend. Strangely, I am able to adjust the width and height of the svg element without any issues. Here is the issue illustrated: https://i.ssta ...

Is it possible to customize the information displayed in a Mat-Dialog based on the object being

My current project involves the presentation of various boxes on a screen. Each box contains a button that, when clicked, redirects to another page. Here is the interface for the box object: export interface Allbox { image: string, link: string, ...

Error encountered: The combination of NextJS and Mongoose is causing a TypeError where it is unable to read properties of undefined, specifically when trying

Versions: Next.js 14.1 React 18 I am currently developing a profile section where users can update their profile information such as username, name, and profile photo. To achieve this, I have implemented a component that contains a form (using shadcn) to ...

I am unable to utilize ES6 modules alongside typescript in my Node.js project

My Node.js project requires the use of both typescript and es6 modules for compiling files to javascript. The desired outcome is to have the files compiled in javascript with the es6 module. Below is the content of my package.json : { "name": ...

In my efforts to reset the TypeORM MySQL database upon server shutdown in NestJS, I am exploring different approaches

I am looking for a way to clear all entries in my database when the server shuts down. Can anyone help with this? export class RoomsService { async onApplicationShutdown() { await this.roomService.deleteAll() } async deleteAll(): Promise<Delete ...

Error message: "Mismatched data types in Formik errors when utilizing TypeScript"

I have a customized input component for formik which includes an error label if one exists. When I print it like this: {errors[field.name]}, it works. However, {t(errors[field.name]?.toLocaleString())} does not work. import { FieldProps, FormikErrors } ...

Consider pushing items onto an array only once when the condition is met, instead of adding to the array every

I have been tasked with importing Excel files containing customer orders into my web application. The process involves converting the data in the file into an object of arrays, where each array represents a row from the Excel sheet. Once the data is impor ...

What seems to be the issue with my @typescript-eslint/member-ordering settings?

I am encountering an issue where my lint commands are failing right away with the error message shown below: Configuration for rule "@typescript-eslint/member-ordering" is throwing an error: The value ["signature","public-static-field","pro ...

Utilizing Prisma's Create Object to Store Return String from Imported Function in Database

In my application, I am utilizing Typescript and have created a test to populate a database using Prisma ORM. Within this test, there is a function that returns a string: const mappingPayload = (data: any) => { let pay = [] const payload = data[0] // ...

Solving the Path Dilemma in TypeScript Functions within the Firebase Environment

My Firebase project utilizes TypeScript functions with the following directory structure: - functions - src - index.ts - shared - other.ts - tsconfig.json - package.json Within my tsconfig.json file, the configuration is as follows: { &q ...

There was an unhandled exception that occurred due to a missing file or directory with the lstat error on 'D:a1s ode_modulesquill'

ngx-quill is causing issues in production, any suggestions? I am currently using "ngx-quill": "^13.4.0", but it is unable to find Quill on my server even though it works locally. The problem persists in the pipeline... An unhandled exception has occurred ...

Challenges with implementing asynchronous functions in NestJS controllers

Currently, I am in the process of developing a finance tracker application that involves importing data from a CSV file. The import functionality checks if an entry already exists in the database, adds a specific category to it if not found, and then saves ...

Angular 9: Implementing a synchronous *ngFor loop within the HTML page

After receiving a list of subjects from the server, exercises are taken on each subject using the subject.id (from the server) and stored all together in the subEx variable. Classes are listed at the bottom. subjects:Subject[] temp:Exercise[] = [] s ...

Setting input limits in AlertBox in Ionic v3: A step-by-step guide

I am currently working on creating an alert box that includes some inputs. I am trying to restrict the input to a maximum of 10 characters and ensure that only numbers are allowed. Unfortunately, I haven't been able to find any helpful guides on this ...

A versatile method to organize a multi-dimensional array of items

I need help sorting a nested array using a generic function. The sorting should be based on the values of the items within the nested array. Here is an example of my array: type Person = { id: number, name: string, childs: Child[] } type Chil ...

Transform the Standard class into a generic one in typescript

I've created a class that can take JSON objects and transform them into the desired class. Here's the code: import {plainToClass} from "class-transformer"; import UserDto from "../../auth/dto/user.dto"; class JsonConverter { ...

JavaScript code that displays values that are not equal

Here is a code snippet that checks whether two arrays of objects are equal or not. How can we enhance this code to log which answer is not matching? The structure of the arrays: arrayA represents user answered questions, while arrayB contains correct answ ...

Jest test encounters Firebase initialization error

Testing event handlers for a slack bolt app has been quite the rollercoaster. Initially, all tests passed flawlessly, making life wonderful. However, after some refactoring, the entire test suite failed to run, displaying an error indicating that firebase ...