Reputation: 1738
I am using Reactive Forms to create a page for managing roles and permissions in my application. There are multiple tables of checkboxes that turn on or off a permission on a role, and I am trying to dynamically create these elements using *ngFor. The problem is, my form element only has a boolean value (on or off) and there is corresponding metadata that is needed (display name, list of claims, etc.) that I need to retrieve to display the element properly. My problem is, as I am looping through the form control collection, I need to match it up to the view model (essentially need to get a variable set in my html template for both the model and the form control.
Here is an example of what the page looks like:
here is my form builder:
this.settingsForm = this.fb.group({
roleName: this.exampleRole.name,
dashboard: '',
settingsPermissions: this.fb.array(this.settingsPermissions.map(perm => this.fb.group({
name: perm.title,
permission: perm.val(this.exampleRole.permissions),
}))),
customerProfilePermissions: this.fb.group({
//accessProfiles: {value: true, description:"Some test description", displayName: "Access Customer Profiles" },
accessProfiles: true,
accessPII: false,
editProfiles: false,
linkProfiles: false,
my example definition for the settings permission object:
export class PermissionConfiguration {
title!: string;
permissionClaims: string[] = [];
description: string;
}
and an example of what my template looks like:
<div *ngIf="settingsForm.get('customerProfilePermissions.accessProfiles').value">
<div class="row">
<div class="col-3 col-xl-2">Access To PII </div>
<div class="col-3 col-xl-2"><k-checkbox formControlName="accessPII"></k-checkbox></div>
<div class="col-3 col-xl-2">Ability to view customer personally identifiable information.</div>
</div>
<div class="row">
<div class="col-3 col-xl-2">Edit Profiles </div>
<div class="col-3 col-xl-2"><k-checkbox formControlName="editProfiles"></k-checkbox></div>
<div class="col-3 col-xl-2">Ability to edit certain information on the customer profile tab. Editable fields are defined on a per-client basis during implementation.</div>
</div>
<div class="row">
<div class="col-3 col-xl-2">Link Related Profiles </div>
<div class="col-3 col-xl-2"><k-checkbox formControlName="linkProfiles"></k-checkbox></div>
<div class="col-3 col-xl-2">Ability to add/remove related profiles in the top right of each customer profile.</div>
</div>
what I need is to be able to get the PermissionConfiguration object from the component along with the form control. If I can do that, then I can replace the name and description fields and make the form dynamic.
In the settings Permissions section (different model object) I did the following and it works but is highly inefficient:
<tr formArrayName="settingsPermissions"
*ngFor="let info of settingsForm.get('settingsPermissions')['controls']; let i = index;">
<ng-container [formGroupName]="i">
<td>{{ getPermModel(info.value.name).title }}</td>
<td><k-checkmark-radio formControlName="permission" valueName="Hide" [groupName]="info.value.name" ></k-checkmark-radio></td>
<td><k-checkmark-radio formControlName="permission" valueName="View" [groupName]="info.value.name"></k-checkmark-radio></td>
<td><k-checkmark-radio formControlName="permission" valueName="Edit" [groupName]="info.value.name"></k-checkmark-radio></td>
<td>{{ getPermModel(info.value.name).description}} </td>
</ng-container>
</tr>
Update
GetPermModel Implementation:
getPermModel(name:string): SettingsPermission {
let model = this.settingsPermissions.find(p => p.title == name);
return model;
}
Upvotes: 2
Views: 1326
Reputation: 4993
I can imaging two ways to improve it:
this.fb.array(this.settingsPermissions.map(perm => this.fb.group({
name: perm.title,
permission: perm.val(this.exampleRole.permissions),
permModel: this.fb.group({
value: this.getPermModel(perm.title)),
disabled: !this.isResetPassword
}),
}))),
and use it in the HTML
<tr formArrayName="settingsPermissions"
*ngFor="let info of settingsForm.get('settingsPermissions')['controls']; let i = index;">
<ng-container [formGroupName]="i">
<td>{{ info.value.permModel.title }}</td>
<td><k-checkmark-radio formControlName="permission" valueName="Hide" [groupName]="info.value.name" ></k-checkmark-radio></td>
<td><k-checkmark-radio formControlName="permission" valueName="View" [groupName]="info.value.name"></k-checkmark-radio></td>
<td><k-checkmark-radio formControlName="permission" valueName="Edit" [groupName]="info.value.name"></k-checkmark-radio></td>
<td>{{ info.value.permModel.description}} </td>
</ng-container>
</tr>
The form value would contain the permModel
field, which might be not desirable.
getPermModel
@Pipe({
name: 'myCustomPipe',
pure: true <----- here (default is `true`)
})
export class MyCustomPipe {
constructor(private settingsPermissions) { }
transform(name:string): SettingsPermission {
let model = this.settingsPermissions.find(p => p.title == name);
return model;
}
}
settingsPermissions
might be also a pipe param if it's not a good candidate to be injectable.
Upvotes: 1
Reputation: 720
If I am understanding the issue correctly you are trying to eliminate this line because you correctly identified that Angular's change detection is going to make the function call expensive,
<td>{{ getPermModel(info.value.name).description}} </td>
I recommend writing a custom angular pipe to do that processing for you. You can pass in your settings configuration and your form value, and do all of the processing that getPermModel() function does with the benefit of the pipe only running when the input changes, rather than running on every change detection cycle like it will with the function in the template.
Here are the basics of implementing a pipe, https://angular.io/guide/pipes
It will look something like this,
@Pipe({name: 'getPermModel'})
export class GetPermModelPipe implements PipeTransform {
transform(name: string, settings: PermissionsConfiguration): string {
return this.settings.find(p => p.title == name);
}
}
Then in your template,
<td>{{ (info.value.name | getPermModel : settingsConfig ).description}} </td>
Upvotes: 1