Eu sei que os Sinais Angulares podem ser usados neste caso em particular para evitar chamar a função isVisible constantemente. Mas não tenho experiência com esse novo recurso e não consigo descobrir o que mudar no meu código para aproveitar os Sinais. Colei um trecho simplificado do meu código abaixo, apenas para a ideia geral.
O que devo mudar para que isso funcione com o 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>
}
[EDITAR]
Seguindo as duas respostas muito boas abaixo, e considerando que ainda estou usando o Angular 18 e não posso usar o LinkedSignal por enquanto, foi assim que acabei fazendo:
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>
}
O truque que eu uso é começar com o estado em um
Signal
/WritableSignal
. Isso geralmente está em um limite de componente/serviço, então é um bom candidato parainput()
ormodel()
também:Então eu componho a partir daí usando
computed()
. Sejam fatias inteiras:Ou chaves individuais:
Então eu vinculo as fatias onde elas são necessárias:
O truque é perceber que o único estado que você precisa rastrear é
sectionConfig
. Todos os outros estados (incluindovisibleSections
) são estados derivados. Seria tentador criar outroSignal
/WritableSignal
paravisibleSections
e então usareffect()
para atualizá-lo quandosectionConfig
houver alterações, mas isso não é necessário e só levaria a mais problemas (confusão, ter que definirallowSignalWrites
, etc).Isso é super similar à maneira como eu uso o RxJS também. Ele está compondo um pipeline dos dados originais (a lista de configurações) para os dados desejados (a visibilidade de certas chaves).
A desvantagem é que as propriedades individuais não são dinâmicas. Você precisará criar um
is_X_Visible()
sinal para cada uma. Se você não quiser criá-las (ou não puder porque não conhece as chaves até o tempo de execução), precisará introduzir umPipe
(chamado da mesma forma que você faria com qualquer outro pipe baseado em parâmetros :@if (visibleSections() | contains:'someComponent')
) ou precisará ajustar o formato da composição final para acomodar a pesquisa de tempo de execução desejada:Então, para atualizar, basta passar um novo valor para o nível superior
Signal
/WritableSignal
e o restante será distribuído em cascata pela árvore reativa para você:O primeiro passo é converter
sectionConfig
para um sinal primário.Então, a propriedade
visibleSections
é um estado derivado, então usamoslinkedSignal
para derivar o estado do originalsectionConfig
(observe que estou usandolinkedSignal
em vez decomputed
porque você tem uma ligação bidirecional com o HTML[(ngModel)]="visibleSections"
, como você sabe,computed
não é gravável, então usamoslinkedSignal
).Quando queremos filtrar usando a interface do usuário, precisamos apenas do valor da string, então criamos outro estado derivado para determinar a string de componentes que precisam ser habilitados.
O método change, nós alavancamos
update
o método, que fornece o valor do sinal. Aqui, procuramos o valor selecionado e então alternamos o sinalizador. O ponto mais importante que você precisa notar é que, usamosarray destructuring
para criar uma nova referência de memória (Arrays e objetos são armazenados como referências de memória), o sinal verifica se o valor foi alterado. Então, devemos alterar a referência de memória do array, somente então o sinal será considerado como o valor a ser alterado e os estados derivados serão recalculados.No lado HTML, podemos vincular o
visibleSections
engModel
então ocultar/mostrar os componentes, usamos o método arrayincludes
para verificar se o componente deve ser mostrado ou nãovisibleSectionsFlags().includes("someComponent")
.Código completo:
TS:
HTML:
Demonstração do Stackblitz