Skip to main content

Implementing the accounts component

Overview

This guide will walk you through the process of implementing a new component that lists all the accounts in your tenant. This serves as an example you can use to learn how to implement many different types of custom UI pages.

This guide is a continuation of Getting Started. At the end of this guide, you will be able to do the following:

  • List all accounts in your tenant
  • Paginate accounts by page limits of 10, 25, 50, 100
  • Click through each page of results
  • View the JSON response of the account from your tenant
  • Sort the results, using the sort syntax on the List Accounts endpoint.
  • Filter the results, using the filter syntax on the List Accounts endpoint.

Create the accounts component

Create the accounts component by running the following command in the root of the project:

npm run generate:component accounts

This command will create a new folder in the projects/sailpoint-components/src/lib directory with the name accounts. It will also create the following files:

  • accounts.component.html
  • accounts.component.ts
  • accounts.component.scss
  • accounts.component.spec.ts

The output will look similar to this:

tyler.mairose ~/development/ui-development-kit [main] $ npm run generate:component accounts

> sailpoint-ui-development-kit@1.0.0 generate:component
> node scripts/generate-component.js accounts

🚀 Generating component: accounts
✅ Created directory: /Users/tyler.mairose/development/ui-development-kit/projects/sailpoint-components/src/lib/accounts
✅ Created: accounts.component.ts
✅ Created: accounts.component.html
✅ Created: accounts.component.scss
✅ Created: accounts.component.spec.ts
✅ Updated: component-selector.service.ts
✅ Updated: app.routes.ts
✅ Updated: app.component.html
✅ Updated: public-api.ts
🎉 Component 'accounts' generated successfully!
📝 Next steps:
1. Build the project: npm run start
2. Enable the component in the component selector
3. Implement your component logic in: /Users/tyler.mairose/development/ui-development-kit/projects/sailpoint-components/src/lib/accounts

Enable the accounts component

To enable the accounts component, open the component selector and toggle the accounts component on. You can do this by clicking on the "Component Selector" tab in the sidebar.

enable account component

You will see the accounts component in the list of available components. Toggle the accounts component on to enable it.

Once enabled you will see the accounts component in the sidebar. You can click on it to navigate to the accounts page.

accounts page

Get account data

The first thing your new component needs to do is get the account data to show. You can implement this by using the List Accounts endpoint.

To do so, add this logic to the typescript file at projects/sailpoint-components/src/lib/accounts/accounts.component.ts:

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatPaginatorModule } from '@angular/material/paginator';
import { SailPointSDKService } from '../sailpoint-sdk.service';
import { AccountV2025 } from 'sailpoint-api-client';

@Component({
selector: 'app-accounts',
standalone: true,
imports: [
CommonModule,
MatButtonModule,
MatCardModule,
MatIconModule,
MatTableModule,
MatToolbarModule,
MatProgressSpinnerModule,
MatPaginatorModule
],
templateUrl: './accounts.component.html',
styleUrl: './accounts.component.scss',
})
export class AccountsComponent implements OnInit {
title = 'Accounts';
loading = true;
accounts: AccountV2025[] = [];
displayedColumns: string[] = ['id', 'name', 'nativeIdentity', 'sourceId', 'disabled', 'locked', 'actions'];

constructor(private sdk: SailPointSDKService) {}

ngOnInit() {
// Load initial data
void this.loadAccounts();
}

private async loadAccounts() {
this.loading = true;
try {
const response = await this.sdk.listAccounts();
this.accounts = response.data as AccountV2025[];
console.log('Loaded accounts:', this.accounts);
} catch (error) {
console.error('Error loading accounts:', error);
} finally {
this.loading = false;
}
}

viewAccount(account: AccountV2025): void {
console.log('Viewing account:', account);
}
}


Return to your accounts list page. In the electron app, click View -> Toggle Developer Tools. You will see the response containing the accounts in the console after the page loads.

Now that you have your account data, you need to display the data. You can add a table to the UI and display your results.

To do so, add this code to \accounts\accounts.component.html:

Show code
<div class="accounts-container">
<mat-toolbar color="primary">
<mat-icon>account_box</mat-icon>
<span class="toolbar-title">{{ title }}</span>
</mat-toolbar>

<div class="content">
<!-- Loading spinner -->
<div *ngIf="loading" class="spinner-container">
<mat-spinner diameter="75"></mat-spinner>
</div>

<div *ngIf="!loading" class="table-container">
<!-- Accounts table -->
<table mat-table [dataSource]="accounts" class="mat-elevation-z8" *ngIf="accounts.length">
<!-- ID Column -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let account">{{ account.id }}</td>
</ng-container>

<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let account">{{ account.name || '-' }}</td>
</ng-container>

<!-- Native Identity Column -->
<ng-container matColumnDef="nativeIdentity">
<th mat-header-cell *matHeaderCellDef>Native Identity</th>
<td mat-cell *matCellDef="let account">{{ account.nativeIdentity || '-' }}</td>
</ng-container>

<!-- Source ID Column -->
<ng-container matColumnDef="sourceId">
<th mat-header-cell *matHeaderCellDef>Source</th>
<td mat-cell *matCellDef="let account">{{ account.sourceId || '-' }}</td>
</ng-container>

<!-- Disabled Column -->
<ng-container matColumnDef="disabled">
<th mat-header-cell *matHeaderCellDef>Disabled</th>
<td mat-cell *matCellDef="let account">
<mat-icon *ngIf="account.disabled">check_circle</mat-icon>
<mat-icon *ngIf="!account.disabled">cancel</mat-icon>
</td>
</ng-container>

<!-- Locked Column -->
<ng-container matColumnDef="locked">
<th mat-header-cell *matHeaderCellDef>Locked</th>
<td mat-cell *matCellDef="let account">
<mat-icon *ngIf="account.locked">lock</mat-icon>
<mat-icon *ngIf="!account.locked">lock_open</mat-icon>
</td>
</ng-container>

<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let account">
<button mat-button color="primary" (click)="viewAccount(account)">
<mat-icon>visibility</mat-icon> View
</button>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

<!-- No data message -->
<div *ngIf="accounts.length === 0" class="no-data-message">
No accounts found.
</div>
</div>
</div>
</div>


Save the \accounts\accounts.component.html file and return to the accounts list page. You will see up to 250 accounts in the table.

Pagination

You will likely have more than 250 accounts in your tenant. To handle more than 250 accounts, you must implement pagination on your front end page. Pagination is the process of paging through your records. With pagination, you can handle 1000 accounts in 4 pages of 250, for example.

To implement pagination, add the following code. This code allows you to paginate accounts in groups of 5, 10, 50, 100 and 250, depending on your requirement:

The highlighted portions of the code are the changes made to the previous code to allow pagination.

<div class="accounts-container">
<mat-toolbar color="primary">
<mat-icon>account_box</mat-icon>
<span class="toolbar-title">{{ title }}</span>
</mat-toolbar>

<div class="content">
<!-- Loading spinner -->
<div *ngIf="loading" class="spinner-container">
<mat-spinner diameter="75"></mat-spinner>
</div>

<div *ngIf="!loading" class="table-container">
<!-- Accounts table -->
<table mat-table [dataSource]="accounts" class="mat-elevation-z8" *ngIf="accounts.length">
<!-- ID Column -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let account">{{ account.id }}</td>
</ng-container>

<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let account">{{ account.name || '-' }}</td>
</ng-container>

<!-- Native Identity Column -->
<ng-container matColumnDef="nativeIdentity">
<th mat-header-cell *matHeaderCellDef>Native Identity</th>
<td mat-cell *matCellDef="let account">{{ account.nativeIdentity || '-' }}</td>
</ng-container>

<!-- Source ID Column -->
<ng-container matColumnDef="sourceId">
<th mat-header-cell *matHeaderCellDef>Source</th>
<td mat-cell *matCellDef="let account">{{ account.sourceId || '-' }}</td>
</ng-container>

<!-- Disabled Column -->
<ng-container matColumnDef="disabled">
<th mat-header-cell *matHeaderCellDef>Disabled</th>
<td mat-cell *matCellDef="let account">
<mat-icon *ngIf="account.disabled">check_circle</mat-icon>
<mat-icon *ngIf="!account.disabled">cancel</mat-icon>
</td>
</ng-container>

<!-- Locked Column -->
<ng-container matColumnDef="locked">
<th mat-header-cell *matHeaderCellDef>Locked</th>
<td mat-cell *matCellDef="let account">
<mat-icon *ngIf="account.locked">lock</mat-icon>
<mat-icon *ngIf="!account.locked">lock_open</mat-icon>
</td>
</ng-container>

<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let account">
<button mat-button color="primary" (click)="viewAccount(account)">
<mat-icon>visibility</mat-icon> View
</button>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

<!-- Paginator -->
<mat-paginator
[length]="totalCount"
[pageSize]="pageSize"
[pageIndex]="pageIndex"
[pageSizeOptions]="[5, 10, 25, 100, 250]"
(page)="onPageChange($event)"
showFirstLastButtons
aria-label="Select page of accounts">
</mat-paginator>

<!-- No data message -->
<div *ngIf="accounts.length === 0" class="no-data-message">
No accounts found.
</div>
</div>
</div>
</div>

Return to the accounts list page. You will see the paginator at the bottom of the page. You can now paginate through the accounts in your tenant.

Viewing Details

In this step, we will create a detail view to see the raw json object that represents the underlying data.

To do this, we will implement the already existing viewAccount method to use the GenericDialogComponent that comes with the UI Development Kit. The only thing to change here is just to implement the method as seen below:

  viewAccount(account: AccountV2025): void {
// Format account details as JSON string with indentation
const details = JSON.stringify(account, null, 2);

// Open dialog with account details
this.dialog.open(GenericDialogComponent, {
minWidth: '800px',
data: {
title: `Account Details: ${account.name || account.nativeIdentity || account.id}`,
message: details
}
});
}

Note that we also need to add the dialog to the constructor:

constructor(private sdk: SailPointSDKService, private dialog: MatDialog) {}

And also add the imports:

import { MatDialog } from '@angular/material/dialog';
import { GenericDialogComponent } from '../generic-dialog/generic-dialog.component';

Sort and filter

The last part of the page we may want to implement would be sorting and filtering. With this implementation, the this.filterForm.valueChanges event and onSortChange event will cause the page to reload the accounts with the new sort or filter applied.

The completed component and all code can be seen below:

<div class="accounts-container">
<mat-toolbar color="primary">
<mat-icon>account_box</mat-icon>
<span class="toolbar-title">{{ title }}</span>
</mat-toolbar>

<div class="content">
<!-- Filter panel -->
<div class="filter-panel mat-elevation-z1">
<form [formGroup]="filterForm">
<div class="filter-row">
<mat-form-field class="filter-field">
<mat-label>Name</mat-label>
<input matInput placeholder="Filter by name" formControlName="name">
</mat-form-field>

<mat-form-field class="filter-field">
<mat-label>Source ID</mat-label>
<input matInput placeholder="Filter by source ID" formControlName="sourceId">
</mat-form-field>

<mat-form-field class="filter-field">
<mat-label>Correlated</mat-label>
<mat-select formControlName="correlated">
<mat-option *ngFor="let option of correlatedOptions" [value]="option.value">
{{option.label}}
</mat-option>
</mat-select>
</mat-form-field>

<button mat-stroked-button color="primary" (click)="resetFilters()">
<mat-icon>clear</mat-icon> Clear
</button>
</div>
</form>
</div>

<!-- Loading spinner -->
<div *ngIf="loading" class="spinner-container">
<mat-spinner diameter="75"></mat-spinner>
</div>

<div *ngIf="!loading" class="table-container">
<!-- Accounts table -->
<table mat-table [dataSource]="accounts" matSort [matSortActive]="sortActive"
[matSortDirection]="sortDirection" (matSortChange)="onSortChange($event)"
class="mat-elevation-z8" *ngIf="accounts.length">
<!-- ID Column -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
<td mat-cell *matCellDef="let account">{{ account.id }}</td>
</ng-container>

<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let account">{{ account.name || '-' }}</td>
</ng-container>

<!-- Native Identity Column -->
<ng-container matColumnDef="nativeIdentity">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Native Identity</th>
<td mat-cell *matCellDef="let account">{{ account.nativeIdentity || '-' }}</td>
</ng-container>

<!-- Source ID Column -->
<ng-container matColumnDef="sourceId">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Source</th>
<td mat-cell *matCellDef="let account">{{ account.sourceId || '-' }}</td>
</ng-container>

<!-- Disabled Column -->
<ng-container matColumnDef="disabled">
<th mat-header-cell *matHeaderCellDef>Disabled</th>
<td mat-cell *matCellDef="let account">
<mat-icon *ngIf="account.disabled">check_circle</mat-icon>
<mat-icon *ngIf="!account.disabled">cancel</mat-icon>
</td>
</ng-container>

<!-- Locked Column -->
<ng-container matColumnDef="locked">
<th mat-header-cell *matHeaderCellDef>Locked</th>
<td mat-cell *matCellDef="let account">
<mat-icon *ngIf="account.locked">lock</mat-icon>
<mat-icon *ngIf="!account.locked">lock_open</mat-icon>
</td>
</ng-container>

<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let account">
<button mat-button color="primary" (click)="viewAccount(account)">
<mat-icon>visibility</mat-icon> View
</button>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

<!-- Paginator -->
<mat-paginator
[length]="totalCount"
[pageSize]="pageSize"
[pageIndex]="pageIndex"
[pageSizeOptions]="[5, 10, 25, 100, 250]"
(page)="onPageChange($event)"
showFirstLastButtons
aria-label="Select page of accounts">
</mat-paginator>

<!-- No data message -->
<div *ngIf="accounts.length === 0" class="no-data-message">
No accounts found.
</div>
</div>
</div>
</div>


Error handling

You have now implemented a new page that lists all the accounts in your tenant, and you can now paginate, sort and filter the accounts in your tenant.

Ideally, everything in your custom UIs will work smoothly, but you will likely encounter errors at some point when you're implementing a page. For example, if you provide an invalid filter or sorter, the list accounts endpoint will return a 400 error. You can see that there is a try catch block on the loadAccounts method that currently shows a console.log error but a custom message could be implemented to notify the user of a problem. This is not covered in this part of the tutorial, but with angular, presenting the user about an error is quite trivial.

To learn more about handling errors in your UI, refer to Error Handling.

Discuss

The most valuable resource for ISC developers is the SailPoint Developer Community itself, where ISC users and experts all over the world come together to ask questions and provide solutions.

To learn more about the ISC UI Development Kit and discuss it with SailPoint Developer Community members, go to the SailPoint Developer Community Forum.