我知道在这种特殊情况下可以使用 Angular Signals 来避免不断调用 isVisible 函数。但我没有使用过这个新功能,也不知道该在代码中做哪些更改才能利用 Signals。我在下面粘贴了一段简化的代码,仅供参考。
我应该进行哪些更改才能使其与 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>
}
[编辑]
遵循下面两个非常好的答案,并考虑到我仍然在使用 angular 18 并且暂时不能使用 linkedSignal,这就是我最终的做法:
TS:
type Config = { name: string; hidden: boolean; };
const sectionConfig = signal<Config[]>([ {
name: 'someComponent',
hidden: false,
},
{
name: 'otherComponent',
hidden: false
}]);
visibleSections = computed(() => {
const sections = this.sectionConfig();
return sections.filter(s => !s.hidden);
});
public visibleSectionsFlags = computed(() => {
return this.sectionConfig()
.filter((item: any) => !item.hidden)
.map((item: any) => item.name);
});
public setSectionVisibility(e: any): void {
this.sectionConfig.update((sectionConfig: any) => {
const foundIndex = this.sectionConfig().findIndex(c => c.name === e.itemValue.name);
if (foundIndex > -1) {
sectionConfig[foundIndex].hidden = !sectionConfig[foundIndex].hidden;
}
return [...sectionConfig];
});
}
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 (visibleSectionsFlags().includes("someComponent")) {
<app-some-component></app-some-component>
}
Signal
我使用的技巧是从/中的状态开始WritableSignal
。这通常位于组件/服务边界,因此它也是input()
or的一个很好的候选者model()
:然后我从那里使用 进行创作
computed()
。无论是整个切片:或者单独的键:
然后我将它们绑定到需要的地方:
诀窍在于意识到您必须跟踪的唯一状态是
sectionConfig
。所有其他状态(包括)都是派生状态。创建另一个/然后在更改时使用它来更新visibleSections
它可能会很诱人,但这不是必需的,只会导致更多问题(混乱、必须设置等)。Signal
WritableSignal
visibleSections
effect()
sectionConfig
allowSignalWrites
这与我使用 RxJS 的方式非常相似。它组成了从原始数据(配置列表)到所需数据(某些键的可见性)的管道。
缺点是单个属性不是动态的。您需要
is_X_Visible()
为每个属性创建一个信号。如果您不想创建这些(或者您不能创建,因为您直到运行时才知道键),您需要引入一个(调用方式与任何其他基于参数的管道Pipe
相同:)或者您需要调整最终组合的形状以适应所需的运行时查找:@if (visibleSections() | contains:'someComponent')
然后为了更新,你只需将一个新值传递给顶层的
Signal
/WritableSignal
,其余部分就会沿着反应树级联下来:第一步是转换
sectionConfig
为主要信号。然后,该属性
visibleSections
是一个派生状态,因此我们使用linkedSignal
从原始状态派生状态sectionConfig
(请注意,我使用linkedSignal
而不是computed
因为您有两种方式绑定到 HTML[(ngModel)]="visibleSections"
,如您所知computed
是不可写的所以我们使用linkedSignal
)。当我们想使用 UI 进行过滤时,我们只需要字符串值,因此我们创建另一个派生状态来确定需要启用的组件的字符串。
更改方法,我们利用
update
提供信号值的方法。在这里,我们查找选定的值,然后切换标志。您需要注意的最重要的一点是,我们用来array destructuring
创建一个新的内存引用(数组和对象存储为内存引用),信号检查值是否已更改。因此,我们必须更改数组的内存引用,然后信号才会被视为要更改的值,并重新计算派生状态。在 HTML 端,我们可以绑定
visibleSections
到ngModel
,然后隐藏/显示组件,我们使用数组方法includes
来检查是否应该显示组件visibleSectionsFlags().includes("someComponent")
。完整代码:
TS:
HTML:
Stackblitz 演示