I have an angular service that generates a data structure called a Template. A Template is
export interface Template {
id: string;
data: number;
}
My service in full looks like this.
export interface Template {
id: string;
data: number;
}
export type AddTemplate = Omit<Template, 'id' | 'data'>;
export interface TemplateState {
templates: Template[];
}
@Injectable({
providedIn: 'root',
})
export class TemplateService {
private state = signal<TemplateState>({
templates: [],
});
templates = computed(() => this.state().templates);
add$ = new Subject<AddTemplate>();
constructor() {
this.add$.pipe(takeUntilDestroyed()).subscribe((template) =>
this.state.update((state) => ({
...state,
templates: [...state.templates, this.constructNewTemplate(template)],
}))
);
}
private constructNewTemplate(template: AddTemplate) {
return {
...template,
id: Math.floor(Math.random() * 1000000000).toString(),
data: Date.now(),
};
}
}
In my main component, I am using the drag and drop cdk to allow dragging of these templates.
<div cdkDropList (cdkDropListDropped)="drop($event)">
@for(template of templates(); track template.id){
<div class="template" cdkDrag>
<p>id: {{template.id}}</p>
<p>data: {{template.data}}</p>
</div>
}
@empty{
<p>add a template!</p>
}
When I use a signal to display these templates, they update fine for the most part. They even reorder as the user drags the templates up and down.
The problem I'm running into however is when I try to create another computed signal that transforms these values with a function.
generated = computed(() => {
return this.format(this.templates());
});
private format(data: Template[]) {
console.log(data);
return data.map((template, index) => {
return { ...template, generatedIndex: index };
});
}
This works... but it doesn't show the reordered templates whenever the user drags them. This makes me think that the drop event should be reordering the templates from the service rather than directly on the computed signal.
Here's a stackblitz demo I created that displays the problem. Notice how the top 2 arrays update properly when you drag and drop to reorder them, but the bottom one doesn't. Here's the link to the code in the demo.
Any help is appreciated. I think either I need to change my approach with the drop function to call on the template service, or I need to figure out why adding in a format function in a computed signal means that signal only updates when a new template is added.
In drop
event handler you mutate your templates array. When you work with state you should do everything within it using immutable approach.
It means instead of mutating your templates array using moveItemInArray(this.templates(), event.previousIndex, event.currentIndex);
you should update your state with a new reordered array, otherwise you generated
computed signal cannot catch value changes of your source signal.
Change your drop
method's body into
drop(event: CdkDragDrop<Template[]>) {
this.templateService.reorder(event.previousIndex, event.currentIndex);
}
and do your state change in your service like this:
reorder(prevIndex: number, curIndex: number): void {
this.state.update(st => {
const templates = [...st.templates]; // create a copy (new reference) to your templates array
moveItemInArray(templates, prevIndex, curIndex) // mutate your copy
return {
...st,
templates, // return a new state with your reordered templates array copy
}
})
}