I have this scenario, where the parent component will fetch user details and show then on the UI, code below:
@Component({
selector: 'app-root',
imports: [Child, FormsModule],
template: `
<div> User Details</div>
@if(resource.value(); as user) {
<div> Title: {{user.title}}</div>
<app-like-button [likes]="user.id"/>
}
<button (click)="refresh()">Reload User Data</button>
`,
})
export class App {
http = inject(HttpClient);
id = signal(1);
resource: ResourceRef<UserDetails> = rxResource({
request: () => this.id(),
loader: ({ request: id }: any) =>
this.http.get<UserDetails>(
`/${id}`
),
});
refresh() {
this.resource.reload();
}
}
Similarly I have a likes component which is responsible for showing the count and the like/dislike button.
@Component({
selector: 'app-like-button',
imports: [FormsModule],
template: `
<div> Child </div>
<div>likes: {{likes()}}</div>
<button (click)="like()">Like</button>
<button (click)="unLike()">Dislike</button>
`,
})
export class Child {
// local state for this component
likes: ModelSignal<number> = model.required<number>();
constructor() {
effect(() => {
const likesCount = this.likes();
// trigger an API call to save the like button press:
console.log(likesCount);
});
}
like() {
this.likes.update((likesPrev: number) => likesPrev + 1);
}
unLike() {
this.likes.update((likesPrev: number) => likesPrev - 1);
}
}
In the below code, I have an effect which reacts to changes of the likes
signal, by react I mean the changes are saved to the database using a put
API call.
The problem is that, I am using an effect which reacts to likes
signal changes. So during initialization the PUT
API call is called, which is not necessary.
I would like the API to react to only local state updates of likes
signal.
Stackblitz Demo
Expected result, is that API call ( represented by console.log
) should not fire on initial load.
I have this scenario, where the parent component will fetch user details and show then on the UI, code below:
@Component({
selector: 'app-root',
imports: [Child, FormsModule],
template: `
<div> User Details</div>
@if(resource.value(); as user) {
<div> Title: {{user.title}}</div>
<app-like-button [likes]="user.id"/>
}
<button (click)="refresh()">Reload User Data</button>
`,
})
export class App {
http = inject(HttpClient);
id = signal(1);
resource: ResourceRef<UserDetails> = rxResource({
request: () => this.id(),
loader: ({ request: id }: any) =>
this.http.get<UserDetails>(
`https://jsonplaceholder.typicode/todos/${id}`
),
});
refresh() {
this.resource.reload();
}
}
Similarly I have a likes component which is responsible for showing the count and the like/dislike button.
@Component({
selector: 'app-like-button',
imports: [FormsModule],
template: `
<div> Child </div>
<div>likes: {{likes()}}</div>
<button (click)="like()">Like</button>
<button (click)="unLike()">Dislike</button>
`,
})
export class Child {
// local state for this component
likes: ModelSignal<number> = model.required<number>();
constructor() {
effect(() => {
const likesCount = this.likes();
// trigger an API call to save the like button press:
console.log(likesCount);
});
}
like() {
this.likes.update((likesPrev: number) => likesPrev + 1);
}
unLike() {
this.likes.update((likesPrev: number) => likesPrev - 1);
}
}
In the below code, I have an effect which reacts to changes of the likes
signal, by react I mean the changes are saved to the database using a put
API call.
The problem is that, I am using an effect which reacts to likes
signal changes. So during initialization the PUT
API call is called, which is not necessary.
I would like the API to react to only local state updates of likes
signal.
Stackblitz Demo
Expected result, is that API call ( represented by console.log
) should not fire on initial load.
1 Answer
Reset to default 0You can use a model
to accept the likes
from the parent component. Why I am doing this is because the model
has a subscribe
property (No need to unsubscribe automatically unsubscribed), which fires only when the local state is updated through set
or update
models.
@Component({
selector: 'app-like-button',
imports: [FormsModule],
template: `
<div> Child </div>
<div>likes: {{likes()}}</div>
<button (click)="like()">Like</button>
<button (click)="unLike()">Dislike</button>
`,
})
export class Child {
// local state for this component
likes: ModelSignal<number> = model.required<number>();
constructor() {
// subscription is auto unsubscribed on component destroy:
this.likes.subscribe((value: number) => {
// trigger an API call to save the like button press:
console.log(value);
});
}
Full Code:
import {
Component,
inject,
input,
model,
ModelSignal,
ResourceRef,
signal,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { provideHttpClient, HttpClient } from '@angular/common/http';
import { rxResource } from '@angular/core/rxjs-interop';
export interface UserDetails {
userId: number;
id: number;
title: string;
completed: boolean;
}
@Component({
selector: 'app-like-button',
imports: [FormsModule],
template: `
<div> Child </div>
<div>likes: {{likes()}}</div>
<button (click)="like()">Like</button>
<button (click)="unLike()">Dislike</button>
`,
})
export class Child {
// local state for this component
likes: ModelSignal<number> = model.required<number>();
constructor() {
// subscription is auto unsubscribed on component destroy:
this.likes.subscribe((value: number) => {
// trigger an API call to save the like button press:
console.log(value);
});
}
like() {
this.likes.update((likesPrev: number) => likesPrev + 1);
}
unLike() {
this.likes.update((likesPrev: number) => likesPrev - 1);
}
}
@Component({
selector: 'app-root',
imports: [Child, FormsModule],
template: `
<div> User Details</div>
@if(resource.value(); as user) {
<div> Title: {{user.title}}</div>
<app-like-button [likes]="user.id"/>
}
<button (click)="refresh()">Reload User Data</button>
`,
})
export class App {
http = inject(HttpClient);
id = signal(1);
resource: ResourceRef<UserDetails> = rxResource({
request: () => this.id(),
loader: ({ request: id }: any) =>
this.http.get<UserDetails>(
`https://jsonplaceholder.typicode/todos/${id}`
),
});
refresh() {
this.resource.reload();
}
}
bootstrapApplication(App, {
providers: [provideHttpClient()],
});