Short answer: you're doing it right.
ReadonlyMap<K, V>
is essentially a supertype of Map<K, V>
since the methods and properties it does have match up with those of Map<K,V>
. So by returning a Map
as a ReadonlyMap
, all you're doing is widening the type of the value. By the way, that means you can skip the type assertion:
public publicApiMethod(): ReadonlyMap<K, V> {
const map: Map<K, V> = new Map(...);
return map; // no assertion necessary
}
Just like you could do this:
public anotherMethod(): string | number {
return "hey"; // always "hey", but assignable to string | number
}
The benefit is that a caller is not free to assume that the returned value has any of the other Map
methods, and trying to set or clear the map contents will produce a compile time error, despite the fact that those methods would indeed exist at runtime. (Or for the anotherMethod()
example, the caller cannot know that the return value is always a string
and can't directly call any string-specific methods on it.)
This compile-time prohibition of runtime behavior is similar to the way the readonly
modifier works. When a property is readonly
, TypeScript will complain if you try to modify it, even though the modification would succeed at runtime.
You could, if you wanted, implement your own version of ReadonlyMap
that is read-only even at runtime, but that's only worth the effort if you have a use case that requires it. If you don't, (and you probably don't) then don't worry about it and keep doing it the way you're doing it.