Readonly is a typescript keyword that makes the property read-only in a class, interface, or type alias. We make a property read-only by prefixing the property as readonly
. We can assign a value to the readonly property only when initializing the object or within a constructor of the class. Any other assignments will result in a compiler error.
Table of Contents
Creating Read-Only Properties in TypeScript
To create a read-only property, we prefix the keyword readonly
before the property name.
In the example below the price
property is marked as readonly
. We can assign a value to the price
property when we initialize the object. However, we cannot change its value afterward. If we try to assign a value compiler throws an Cannot assign to 'price' because it is a read-only property
error.
Creating Objects with readonly property
1 2 3 4 5 6 | let product : {name:string, readonly price:number} = { name:"iPhone 13 Pro", price:1} product.name="iPhone 13 Pro" //ok product.price=100; //Cannot assign to 'price' because it is a read-only property. |
readonly property in Interface
1 2 3 4 5 6 7 8 9 10 | interface Product {name:string, readonly price:number} let product:Product= { name:"iPhone 13 Pro", price:1} product.name="iPhone 13 Pro" product.price=100; //Cannot assign to 'price' because it is a read-only property. |
readonly property in Type Alias
1 2 3 4 5 6 7 8 9 10 | type Product = {name:string, readonly price:number} let product:Product= { name:"iPhone 13 Pro", price:1} product.name="iPhone 13 Pro" product.price=100; //Cannot assign to 'price' because it is a read-only property. |
Class and readonly property
In a class, we can assign the value to the readonly property only inside the construction function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Product { name:string readonly price:number constructor(name:string, price:number) { this.name=name this.price=price } } let product= new Product("iPhone 13 Pro", 1) product.name="iPhone 13 Pro" product.price=100; //Cannot assign to 'price' because it is a read-only property. |
Readonly is a read-only reference to a value
We cannot assign a new value to readonly property. But if the value is an object, you can modify the object itself.
The person
interface below has an address property that is readonly. We create a person object from it.
1 2 3 4 5 6 7 8 9 10 11 12 13 | interface Person { name:string, readonly address:Address } interface Address { city: string state:string }; let person:Person = {name:"Eric J. Cerda", address:{city:"Belmont",state:"California"}} |
We cannot assign a new address to the address
property because address
is readonly.
1 2 3 4 | //Cannot assign to 'address' because it is a read-only property. person.address= {city:"Belmont",state:"Massachusetts"} |
But address itself is an object and contains properties. You can change them
1 2 3 4 5 | //But this allowed person.address.city="Belmont" person.address.state="Massachusetts" |
You can solve this problem by making all the properties of interface Address as readonly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | interface Person { name:string, readonly address:Address } interface Address { readonly city: string readonly state:string }; let person:Person = {name:"Eric J. Cerda", address:{city:"Belmont",state:"California"}} //Cannot assign to 'address' because it is a read-only property. person.address= {city:"Belmont",state:"Massachusetts"} //Cannot assign to 'city' because it is a read-only property. person.address.city="Belmont" //Cannot assign to 'state' because it is a read-only property. person.address.state="Massachusetts" |
Types that differ only in their readonly attributes are mutually assignable
This example has two similar interfaces RW
& RO
with value property. Value property has a readonly attribute in RO
.
We create a variable ro
using the interface RO
. Since the value
is readonly we cannot assign a value to it.
We create rw
variable using the RW
interface and assign ro
to it. Now both ro
& rw
points to the same object. Now you can assign value to the value property using the variable rw
effectively bypassing the readonly attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | interface RW { value: number; } interface RO { readonly value: number; } let ro: RO = { value: 10 }; ro.value = 20; // compile-time error as expected. value is readonly let rw:RW = ro; //This should not have been allowed rw.value = 20 console.log(ro.value) //20 console.log(rw.value) //20 |
You can follow this issue here
Readonly exists only in compile time
The readonly
is specific to TypeScript and does not exist in run time. Typescript compiler uses it to check for illegal property assignments in compile time. So the variable does not have any protection in the run-time. Once the code is transpiled into JavaScript readonly
is gone.
Readonly vs. const
It may appear that the const keyword in Typescript is similar to readonly but they are not. Both have several differences
const apply to variables only. readonly for properties
Trying to define price
as const throws an error. In fact, you cannot use let, var & const in property declarations. We use const to declare variables and not properties.
1 2 3 4 5 6 7 8 9 10 11 | class Product { name:string const price:number=0 //A class member cannot have the 'const' keyword. constructor(name:string, price:number) { this.name=name this.price=price } } |
const
is used to create variables. In this example, const
keyword is used to create the prd
variable. You cannot assign a new value to the prd
variable. But you can modify the object that the const variable points to.
1 2 3 4 5 6 7 8 | //Creating a const variable const prd=new Product("iphone ",1000) prd=new Product("iphone Pro Max",2000) //Cannot assign to 'prd' because it is a constant. prd.price=2000 //ok |
Similarly, you cannot prefix a variable with readonly or declare a variable with readonly
1 2 3 4 | let readonly prd1=new Product("iphone ",1000) //',' expected. readonly prd2=new Product("iphone ",1000) //eclaration or statement expected. |
const is runtime check readonly is compile-time check
readonly is only a compile-time check. The compiler removes it when it compiles it into JavaScript.
const is a JavaScript construct. Hence exists in run time. You cannot assign a new value to const
variable ever. But note that the object pointed by the const
can be modified.
Make all properties readonly in an existing type
Readonly utility type creates a new type from an existing type with all properties of the new type set to readonly
. This means that we cannot assign values to the properties of any object which we create from it.
1 2 3 | Readonly<exitingType> |
In this example, we create two new objects from the interface Product. The product2
is created after transforming the interface using the Readonly utility. Hence while assigning a value to any of the properties of the product2
compiler throws an error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { name:string price:number } let product1:Product= {name:"iPhone 13 Pro", price: 100} product1.name="iPhone 13 Pro" //ok product1.price=100; //ok let product2:Readonly<Product>= {name:"iPhone 13 Pro", price: 100} product2.name="iPhone 13 Pro" //Cannot assign to 'name' because it is a read-only property. product2.price=100; //Cannot assign to 'price' because it is a read-only property. |
In this example, the address
is readonly along with all of its properties.
1 2 3 4 5 6 7 8 9 10 11 | interface Person { name:string, readonly address:Readonly<Address> } interface Address { city: string state:string }; |
vs Object.freeze
JavaScript’s object.freeze
method freezes an object. You cannot assign a new value to an existing property of a frozen object effectively making it a read-only property. Also, you cannot add or remove a property from the frozen object. It is a run-time check and no code can change the value of the property.
While Typescripts Readonly is only a compile-time check. Other libraries can modify the value of the Read-only properties
In this example, we create a readonly product1
object. While the compiler will not allow us to assign a value to price
, you override it by casting it to any
and assigning a value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { name:string price:number } let product1:Readonly<Product> = {name:"iPhone 13 Pro", price: 100} //Compile time error Cannot assign to 'price' because it is a read-only property. product1.price=100; //But this is ok (product1 as any).price=1000 //Price is changed to 1000 console.log(product1) //{ "name": "iPhone 13 Pro", "price": 1000 } |
But if you freeze the object and try to do the same, the compiler won’t object. But the JavaScript throws a runtime error and execution stops.
1 2 3 4 5 6 7 8 9 10 11 12 | interface Product { name:string price:number } let product2:Readonly<Product> = Object.freeze({name:"iPhone 13 Pro", price: 100}); (product2 as any).price=1000 //Runtime error. Executed JavaScript Failed: Cannot assign to read only property 'price' of object '#<Object> console.log(product2) //this will not be executed |
Hence use object.freeze
if you want to ensure no changes to the value both in run time or in compile-time and use Readonly if the compile-time check is sufficient.