I know that Angular Signals could be used in this particular case in order to avoid calling the isVisible function constantly. But I have no experience with this new feature and I cannot figure out what to change in my code to take advantage of Signals. I pasted a simplified snippet of my code below, just for the general idea.
What should I change to make this work with Signals ?
visibleSections = [];
sectionConfig = [
{
name: 'someComponent',
hidden: false
},
{
name: 'otherComponent',
hidden: false
}
];
ngOnInit() {
this.visibleSections = this.sectionConfig.filter(c => !c.hidden);
}
public isVisible(section): boolean {
return this.visibleSections.findIndex(s => s.name === section) > -1;
}
public setSectionVisibility(e: any): void {
this.sectionConfig.find(c => c.name === e.itemValue.name).hidden = e.value.findIndex(p => p === e.itemValue) === -1;
}
HTML :
<p-multiSelect
#multiselect
defaultLabel="Show/hide sections"
optionLabel="title"
name="show-hide-sections"
scrollHeight="300px"
styleClass="multiselect-as-button mr-3 ml-3"
dropdownIcon="fas fa-bars rotate-90"
[options]="sectionConfig"
[(ngModel)]="visibleSections"
[displaySelectedLabel]="false"
(onChange)="setSectionVisibility($event)"
[style]="{ width: '208px' }"
[showHeader]="false"
[appendTo]="'body'"></p-multiSelect>
@if (isVisible("someComponent")) {
<some-component></some-component>
}
@if (isVisible("otherComponent")) {
<other-component></other-component>
}
2
Answers
The trick I use is to start with the state in a
Signal
/WritableSignal
. This is usually at a component/service boundary, so it’s a good candidate forinput()
ormodel()
too:Then I compose from there using
computed()
. Whether it’s whole slices:Or individual keys:
Then I bind to the slices where they’re needed:
The trick is to realize that the only state you have to track is
sectionConfig
. All the other state (includingvisibleSections
) is derived state. It would be tempting to create anotherSignal
/WritableSignal
forvisibleSections
and then useeffect()
to update it whensectionConfig
changes, but that isn’t necessary and would only lead to more problems (confusion, having to setallowSignalWrites
, etc).This is super similar to the way I use RxJS too. It’s composing a pipeline from the original data (the list of configurations) to the desired data (the visibility of certain keys).
The downside is that the individual properties aren’t dynamic. You’ll need to create an
is_X_Visible()
signal for each. If you don’t want to create these (or you cannot because you don’t know the keys until runtime), you’ll need to introduce aPipe
(called the same way you would any other parameter-based pipe:@if (visibleSections() | contains:'someComponent')
) or you will need to adjust the shape of the final composition to accommodate the desired runtime lookup:Then for updating, you just pass a new value to the top-level
Signal
/WritableSignal
and the rest will cascade down the reactive tree for you:The first step is to convert
sectionConfig
to a primary signal.Then, the property
visibleSections
is a derived state, so we uselinkedSignal
to derive the state from the originalsectionConfig
(Notice I am usinglinkedSignal
instead ofcomputed
because you have two way binded to the HTML[(ngModel)]="visibleSections"
, as you knowcomputed
is not writable so we uselinkedSignal
).When we want to filter using UI, we only need the string value, so we create another derived state to determine the string of components that need to be enabled.
The change method, we leverage
update
method, which provides the value of the signal. Here we lookup the selected value and then toggle the flag. The most important point you need to notice is that, we usearray destructuring
to create a new memory reference (Arrays and objects are stored as memory references), the signal checks the value has been changed. So we must change the memory reference of the array only then signal will consider as the value to be changed and the derived states will be recomputed.In the HTML side, we can bind the
visibleSections
tongModel
and then to hide/show the components, we use the array methodincludes
to check if the component should be shown or notvisibleSectionsFlags().includes("someComponent")
.Full Code:
TS:
HTML:
Stackblitz Demo