A few days ago, I was teaching Angular, and people asked me why I was doing this with my signals inside Angular services:

readonly data = signal('value').asReadonly();

Why am I using read-only twice? Isn’t this redundant?

First, let’s say I only do this:

export class DataService {
data = signal('value').asReadonly();
}

Other components can inject that service, and the following is prevented, which is good if we want to ensure components can’t change the value of a Signal:

export class AppComponent {

data = inject(DataService).data;

constructor() {
// This doesn't compile because our signal isn't writable
data.set("New value");
}

But a developer could still break things and create all sorts of problems by doing the following:

export class AppComponent {

constructor() {
// This works!!!
inject(DataService).data = signal("other data");
// And this too, because the signal isn't read-only anymore
inject(DataService).data.set("something else");
}

Now, that’s dangerous because a component could decide to replace the state of your service with another signal — and that’s a signal that isn’t even read-only anymore!

The easiest way to prevent this is to use the double read-only syntax I introduced earlier:

export class DataService {
readonly data = signal('value').asReadonly();
}

Now, our component cannot change the reference anymore, as `readonly` turns it into a constant of sorts that can only be updated in the service class, not anywhere else:

export class AppComponent {

constructor() {
// This doesn't compile anymore - our code is safe
inject(DataService).data = signal("other data");
}

Our service is improved but still has a couple of problems:

export class DataService {

readonly data = signal('value').asReadonly();

constructor() {
// This works, but it turns our signal into a writable one
this.data = signal("other data");
// Unless we do this - then we can't update the value anymore
this.data = signal("other data").asReadonly();
}

The solution is to have a private, read-only WritableSignal in our service and then expose a public, entirely read-only version of it:

export class DataService {
// This is the signal our service works with
private readonly dataSignal = signal('value');
// This is the public, "locked" version of that same Signal
readonly data = this.dataSignal.asReadonly();

constructor() {
// This works!!
this.dataSignal.set("other value");
}

This simple technique allows components to consume Signal data without being able to change it directly. Consumers would have to use service methods to change the Signal value, which ensures that any business logic around that update (validation, side effects, etc.) is handled in the service itself, where such logic belongs.

Note that the same technique can be applied to RxJs-based services with Subjects and Observables:

export class DataService {
// This is the subject our service works with
private readonly dataSubject = new BehaviorSubject('value');
// This is the public, "locked" version of that same Subject
readonly data$ = this.dataSubject.asObservable();

constructor() {
// This works!!
this.dataSubject.next("other value");
}

Other options are available, such as using a getter for our Signal and no setter, but these tend to be more verbose than the two lines of code used in the technique mentioned here.

My name is Alain Chautard. I am a Google Developer Expert in Angular and a consultant and trainer at Angular Training, where I help web development teams learn and become comfortable with Angular.

If you need any help with web development, feel free to get in touch!

If you enjoyed this article, please clap for it or share it. Your help is always appreciated. You can also subscribe to my articles and my Daily Angular Newsletter to receive helpful daily tips.

Share.
Leave A Reply