I have this scenario, My service holds few signals (They are use by resource
API's to reactively fetch data and also show at the UI level)
My requirement is that I want to expose these values to the User in the component HTML.
My challenges are:
I generally use a getter and setter, to provide these properties from a service (Private - not accessible in HTML).
I do not want to use a computed
here because, there is no actual computation happening, I just need the service signal reference, this scenario definitely does not get classified as a derived state, it is just a reference to a signal from the service.
I do not want to make my service public, I want to expose certain elements alone, I would keep the extra elements as private, but using service in HTML is not an option for me.
These are my requirements, below is my minimal reproducible code:
@Injectable({
providedIn: 'root',
})
export class SomeService {
http = inject(HttpClient);
serviceIdSignal: WritableSignal<number> = signal(0);
rxResource = rxResource({
request: () => this.serviceIdSignal(),
loader: ({ request: id }) => {
return this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`);
},
});
resource = resource({
request: () => this.serviceIdSignal(),
loader: ({ request: id, abortSignal }) => {
return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
signal: abortSignal,
}).then((r) => r.json());
},
});
}
@Component({
selector: 'app-root',
imports: [JsonPipe],
template: `
<hr/>
{{someService.serviceIdSignal()}}
<hr/>
@if(someService.rxResource.status() === rs.Resolved) {
{{someService.rxResource.value() | json}}
} @else {
Loading...
}
<hr/>
@if(someService.rxResource.status() === rs.Resolved) {
{{someService.resource.value() | json}}
} @else {
Loading...
}
`,
})
export class App {
rs = ResourceStatus;
private someService = inject(SomeService);
}
Below is the error I am getting:
✘ [ERROR] NG1: Property 'someService' is private and only accessible within class 'App'. [plugin angular-compiler]
src/main.ts:56:12: 56 │ {{someService.resource.value() | json}} ╵ ~~~~~~~~~~~
The advantages (might be) of signals is that they are functions and not primitive values, by that I mean, like arrays
and objects
, signal elements (computed
, resource
, rxResource
, linkedSignal
) are stored as memory references
.
You can directly refer these memory references instead of using a computed
(Which as you said in the question, the scenario does not qualify as a derived state, but a referral to the actual state).
My point is, create properties that directly reference the signals from the service and make them public:
export class App {
rs = ResourceStatus;
private someService = inject(SomeService);
// Signals are function, so they are memory reference, you can refer to them directly
// without using getter or computed, which is much simpler!
id: WritableSignal<number> = this.someService.serviceIdSignal; // <- refer directly to the service signal
rxResource: ResourceRef<any> = this.someService.rxResource; // <- refer directly to the service rxResource
resource: ResourceRef<any> = this.someService.resource; // <- refer directly to the service resource
}
You can use these newly created properties as ordinary signal elements, by directly executing them.
<hr/>
{{id()}}
<hr/>
@if(rxResource.status() === rs.Resolved) {
{{rxResource.value() | json}}
} @else {
Loading...
}
<hr/>
@if(rxResource.status() === rs.Resolved) {
{{resource.value() | json}}
} @else {
Loading...
}
import {
Component,
signal,
Injectable,
WritableSignal,
inject,
resource,
ResourceStatus,
ResourceRef,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { JsonPipe } from '@angular/common';
@Injectable({
providedIn: 'root',
})
export class SomeService {
http = inject(HttpClient);
readonly serviceIdSignal: WritableSignal<number> = signal(1);
readonly rxResource = rxResource({
request: () => this.serviceIdSignal(),
loader: ({ request: id }) => {
return this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`);
},
});
readonly resource = resource({
request: () => this.serviceIdSignal(),
loader: ({ request: id, abortSignal }) => {
return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
signal: abortSignal,
}).then((r) => r.json());
},
});
}
@Component({
selector: 'app-root',
imports: [JsonPipe],
template: `
<hr/>
{{id()}}
<hr/>
@if(rxResource.status() === rs.Resolved) {
{{rxResource.value() | json}}
} @else {
Loading...
}
<hr/>
@if(rxResource.status() === rs.Resolved) {
{{resource.value() | json}}
} @else {
Loading...
}
`,
})
export class App {
rs = ResourceStatus;
private someService = inject(SomeService);
// Signals are function, so they are memory reference, you can refer to them directly
// without using getter or computed, which is much simpler!
id: WritableSignal<number> = this.someService.serviceIdSignal; // <- refer directly to the service signal
rxResource: ResourceRef<any> = this.someService.rxResource; // <- refer directly to the service rxResource
resource: ResourceRef<any> = this.someService.resource; // <- refer directly to the service resource
}
bootstrapApplication(App, {
providers: [provideHttpClient()],
});