Skip to content

Instantly share code, notes, and snippets.

@muhammedaltug
Last active October 26, 2023 11:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save muhammedaltug/f43f8a161ea7dfcd8eb788e504cbce44 to your computer and use it in GitHub Desktop.
Save muhammedaltug/f43f8a161ea7dfcd8eb788e504cbce44 to your computer and use it in GitHub Desktop.
How to replace Users Component

Abp users component can be replaced with these steps

  1. Create a new module called IdentityExtendedModule:
yarn ng generate module identity-extended

2.Run the following command to create a new component called UsersComponent:

yarn ng generate component users --inlineStyle --module identity-extended
  1. Open the generated identity-extended.module.ts file in src/app/identity-extended and replace content with following:
import {NgModule} from '@angular/core';
import {IdentityModule} from '@volo/abp.ng.identity';
import {NgbNavModule, NgbTooltipModule} from '@ng-bootstrap/ng-bootstrap';
import {ThemeSharedModule} from '@abp/ng.theme.shared';
import {UiExtensionsModule} from '@abp/ng.theme.shared/extensions';
import {CoreModule} from '@abp/ng.core';
import {PermissionManagementModule} from '@abp/ng.permission-management';
import {NgxValidateCoreModule} from '@ngx-validate/core';
import {UsersComponent} from './users.component';

@NgModule({
  declarations: [UsersComponent],
  imports: [
    IdentityModule.forChild(),
    CoreModule,
    ThemeSharedModule,
    NgbNavModule,
    PermissionManagementModule,
    UiExtensionsModule,
    NgbTooltipModule,
    NgxValidateCoreModule
  ]
})
export class IdentityExtendedModule{}
  1. Open the generated users.component.ts file in src/app/identity-extended and replace content with following:
import {generatePassword, ListService, LocalizationService} from '@abp/ng.core';
import {Confirmation, ConfirmationService, getPasswordValidators, ToasterService} from '@abp/ng.theme.shared';
import {EXTENSIONS_IDENTIFIER, FormPropData, generateFormFromProps} from '@abp/ng.theme.shared/extensions';
import {Component, Injector, OnInit, TemplateRef, TrackByFunction, ViewChild} from '@angular/core';
import {AbstractControl, FormArray, FormBuilder, FormGroup, Validators} from '@angular/forms';
import {Select, Store} from '@ngxs/store';
import {Observable} from 'rxjs';
import {finalize, pluck, switchMap, take, tap} from 'rxjs/operators';
import snq from 'snq';
import {
  CreateUser,
  DeleteUser,
  eIdentityComponents,
  GetUserRoles,
  GetUsers,
  Identity,
  IdentityService,
  IdentityState,
  OrganizationUnitWithDetailsDto,
  UnlockUser,
  UpdateUser,
  UsersComponent as IdentityUsersComponent
} from '@volo/abp.ng.identity';

@Component({
  selector: 'abp-users',
  templateUrl: './users.component.html',
  providers: [
    ListService,
    {
      provide: EXTENSIONS_IDENTIFIER,
      useValue: eIdentityComponents.Users,
    },
    { provide: IdentityUsersComponent, useExisting: UsersComponent },

  ],
  styles: [
    `
      .mh-35 {
        max-height: 35px;
      }
    `,
  ],
})
export class UsersComponent implements OnInit {
  @Select(IdentityState.getUsers)
  data$: Observable<Identity.UserItem[]>;

  @Select(IdentityState.getUsersTotalCount)
  totalCount$: Observable<number>;

  @ViewChild('modalContent')
  modalContent: TemplateRef<any>;

  form: FormGroup;

  setPasswordForm = this.fb.group({
    newPassword: ['', [Validators.required, ...getPasswordValidators(this.store)]],
  });

  selected: Identity.UserItem;

  selectedUserRoles: Identity.RoleItem[];

  roles: Identity.RoleItem[];

  organizationUnits: OrganizationUnitWithDetailsDto[];

  selectedOrganizationUnits: OrganizationUnitWithDetailsDto[];

  visiblePermissions: boolean = false;

  providerKey: string;

  isModalVisible: boolean;

  isSetPasswordModalVisible: boolean;

  modalBusy: boolean = false;

  visibleClaims: boolean = false;

  claimSubject = {} as { id: string; type: 'roles' | 'users' };

  trackByFn: TrackByFunction<AbstractControl> = (index, item) => Object.keys(item)[0] || index;

  get roleGroups(): FormGroup[] {
    return snq(() => (this.form.get('roleNames') as FormArray).controls as FormGroup[], []);
  }

  get organizationUnitGroups(): FormGroup[] {
    return snq(
      () => (this.form.get('organizationUnitIds') as FormArray).controls as FormGroup[],
      [],
    );
  }

  onVisiblePermissionChange = (value: boolean) => {
    this.visiblePermissions = value;
  };

  constructor(
    public readonly list: ListService,
    private confirmationService: ConfirmationService,
    private identityService: IdentityService,
    private fb: FormBuilder,
    private store: Store,
    private toasterService: ToasterService,
    private injector: Injector,
    private localizationService: LocalizationService,
  ) {}

  ngOnInit() {
    this.hookToQuery();
  }

  private hookToQuery() {
    this.list.hookToQuery(query => this.store.dispatch(new GetUsers(query))).pipe(tap(console.log)).subscribe();
  }

  buildForm() {
    const data = new FormPropData(this.injector, this.selected);
    this.form = generateFormFromProps(data);

    this.identityService.getUserAssingableRoles().subscribe(({ items }) => {
      this.roles = items;
      this.form.addControl(
        'roleNames',
        this.fb.array(
          this.roles.map(role =>
            this.fb.group({
              [role.name]: [
                this.selected.id
                  ? !!snq(() => this.selectedUserRoles.find(userRole => userRole.id === role.id))
                  : role.isDefault,
              ],
            }),
          ),
        ),
      );
    });

    this.identityService.getUserAvailableOrganizationUnits().subscribe(({ items }) => {
      this.organizationUnits = items;
      this.form.addControl(
        'organizationUnitIds',
        this.fb.array(
          this.organizationUnits.map(unit =>
            this.fb.group({
              [unit.displayName]: [
                this.selected.id
                  ? !!snq(() => this.selectedOrganizationUnits.find(u => u.id === unit.id))
                  : false,
              ],
            }),
          ),
        ),
      );
    });
  }

  openModal() {
    this.buildForm();
    this.isModalVisible = true;
  }

  onAdd() {
    this.selected = {} as Identity.UserItem;
    this.selectedUserRoles = [] as Identity.RoleItem[];
    this.selectedOrganizationUnits = [] as OrganizationUnitWithDetailsDto[];
    this.openModal();
  }

  onEdit(id: string) {
    this.identityService
      .getUserById(id)
      .pipe(
        tap(selectedUser => (this.selected = selectedUser)),
        switchMap(() => this.store.dispatch(new GetUserRoles(id))),
        pluck('IdentityState'),
        tap(state => (this.selectedUserRoles = state.selectedUserRoles || [])),
        switchMap(() => this.identityService.getUserOrganizationUnits(id)),
        tap(res => (this.selectedOrganizationUnits = res)),
        take(1),
      )
      .subscribe(() => this.openModal());
  }

  save() {
    if (!this.form.valid) return;
    this.modalBusy = true;

    const { roleNames, organizationUnitIds } = this.form.value;
    const mappedRoleNames = snq(
      () =>
        roleNames.filter(role => !!role[Object.keys(role)[0]]).map(role => Object.keys(role)[0]),
      [],
    );
    const mappedOrganizationUnitIds = snq(
      () =>
        organizationUnitIds
          .filter(unit => !!unit[Object.keys(unit)[0]])
          .map(unit => this.organizationUnits.find(u => u.displayName === Object.keys(unit)[0]).id),
      [],
    );

    this.store
      .dispatch(
        this.selected.id
          ? new UpdateUser({
            ...this.selected,
            ...this.form.value,
            id: this.selected.id,
            roleNames: mappedRoleNames,
            organizationUnitIds: mappedOrganizationUnitIds,
          })
          : new CreateUser({
            ...this.form.value,
            roleNames: mappedRoleNames,
            organizationUnitIds: mappedOrganizationUnitIds,
          }),
      )
      .pipe(finalize(() => (this.modalBusy = false)))
      .subscribe(() => {
        this.list.get();
        this.isModalVisible = false;
      });
  }

  delete(id: string, userName: string) {
    this.confirmationService
      .warn('AbpIdentity::UserDeletionConfirmationMessage', 'AbpIdentity::AreYouSure', {
        messageLocalizationParams: [userName],
      })
      .subscribe((status: Confirmation.Status) => {
        if (status === Confirmation.Status.confirm) {
          this.store.dispatch(new DeleteUser(id)).subscribe(() => this.list.get());
        }
      });
  }

  onManageClaims(id: string) {
    this.claimSubject = {
      id,
      type: 'users',
    };

    this.visibleClaims = true;
  }

  unlock(id: string) {
    this.store.dispatch(new UnlockUser(id)).subscribe(() => {
      this.toasterService.success('AbpIdentity::UserUnlocked');
      this.list.get();
    });
  }

  openPermissionsModal(providerKey: string) {
    this.providerKey = providerKey;
    setTimeout(() => {
      this.visiblePermissions = true;
    }, 0);
  }

  setPassword() {
    if (this.setPasswordForm.invalid) return;

    this.modalBusy = true;
    this.identityService
      .changePassword(this.selected.id, this.setPasswordForm.value)
      .pipe(finalize(() => (this.modalBusy = false)))
      .subscribe(() => {
        this.isSetPasswordModalVisible = false;
        this.selected = {} as Identity.UserItem;
        this.setPasswordForm.reset();
      });
  }

  generatePassword() {
    this.setPasswordForm.get('newPassword').setValue(generatePassword());
  }

  getParentName(parentId: string) {
    if (!parentId) return undefined;

    const { displayName } = this.organizationUnits.find(unit => unit.id === parentId);
    return this.localizationService.instant('AbpIdentity::OrganizationUnit:Parent{0}', displayName);
  }
}
  1. Open the generated users.component.html file in src/app/identity-extended and replace content with following:
<ng-container *ngIf="data$ | async as data">
  <div class="row entry-row">
    <div class="col-auto">
      <h1 class="content-header-title">{{ 'AbpIdentity::Users' | abpLocalization }}</h1>
    </div>
    <div class="col-lg-auto pl-lg-0">
      <abp-breadcrumb></abp-breadcrumb>
    </div>
    <div class="col">
      <abp-page-toolbar [record]="data"></abp-page-toolbar>
    </div>
  </div>

  <div id="identity-users-wrapper">
    <div class="card">
      <div class="card-body">
        <div id="data-tables-table-filter" class="data-tables-filter">
          <div class="input-group">
            <input
              type="search"
              class="form-control"
              [placeholder]="'AbpUi::PagerSearch' | abpLocalization"
              [(ngModel)]="list.filter"
            />
            <div class="input-group-append">
              <button class="btn btn-sm btn-primary" (click)="list.get()">
                <i class="fas fa-search"></i>
              </button>
            </div>
          </div>
        </div>
        <abp-extensible-table
          actionsText="AbpIdentity::Actions"
          [data]="data"
          [recordsTotal]="totalCount$ | async"
          [list]="list"
        ></abp-extensible-table>
      </div>
    </div>
  </div>
</ng-container>

<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy" (disappear)="form = null">
  <ng-template #abpHeader>
    <h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser') | abpLocalization }}</h3>
  </ng-template>

  <ng-template #abpBody>
    <form *ngIf="form" [formGroup]="form" (ngSubmit)="save()" validateOnSubmit>
      <ul id="user-nav-tabs" ngbNav #nav="ngbNav" class="nav-tabs">
        <li id="user-informations" ngbNavItem>
          <a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a>
          <ng-template ngbNavContent
            ><abp-extensible-form [selectedRecord]="selected"></abp-extensible-form
          ></ng-template>
        </li>
        <li id="user-roles" ngbNavItem>
          <a ngbNavLink>{{ 'AbpIdentity::Roles' | abpLocalization }}</a>
          <ng-template ngbNavContent>
            <div
              *ngFor="let roleGroup of roleGroups; let i = index; trackBy: trackByFn"
              class="custom-checkbox custom-control mb-2"
            >
              <input
                type="checkbox"
                class="custom-control-input"
                [attr.id]="'roles-' + i"
                [formControl]="roleGroup.controls[roles[i].name]"
              />
              <label class="custom-control-label" [attr.for]="'roles-' + i">{{
                roles[i].name
              }}</label>
            </div></ng-template
          >
        </li>
        <li id="user-organization-units" ngbNavItem>
          <a ngbNavLink>{{ 'AbpIdentity::OrganizationUnits' | abpLocalization }}</a>
          <ng-template ngbNavContent>
            <div
              *ngFor="let unitGroup of organizationUnitGroups; let i = index; trackBy: trackByFn"
              class="custom-checkbox custom-control mb-2"
            >
              <input
                type="checkbox"
                class="custom-control-input"
                [attr.id]="'unit-' + i"
                [formControl]="unitGroup.controls[organizationUnits[i].displayName]"
              />
              <label class="custom-control-label" [attr.for]="'unit-' + i">
                <span [ngbTooltip]="getParentName(organizationUnits[i].parentId)">{{
                  organizationUnits[i].displayName
                }}</span>
              </label>
            </div>
          </ng-template>
        </li>
      </ul>
      <div [ngbNavOutlet]="nav" class="mt-2 fade-in-top"></div>
    </form>
  </ng-template>

  <ng-template #abpFooter>
    <button type="button" class="btn btn-secondary" #abpClose>
      {{ 'AbpIdentity::Cancel' | abpLocalization }}
    </button>
    <abp-button iconClass="fa fa-check" (click)="save()" [disabled]="form?.invalid">{{
      'AbpIdentity::Save' | abpLocalization
    }}</abp-button>
  </ng-template>
</abp-modal>

<abp-permission-management
  *abpReplaceableTemplate="{
    inputs: {
      providerName: { value: 'U' },
      providerKey: { value: providerKey },
      hideBadges: { value: true },
      visible: { value: visiblePermissions, twoWay: true }
    },
    outputs: { visibleChange: onVisiblePermissionChange },
    componentKey: 'PermissionManagement.PermissionManagementComponent'
  }"
  [(visible)]="visiblePermissions"
  [providerKey]="providerKey"
  [hideBadges]="true"
  providerName="U"
>
</abp-permission-management>

<abp-claim-modal [(visible)]="visibleClaims" [subject]="claimSubject"></abp-claim-modal>

<abp-modal [(visible)]="isSetPasswordModalVisible" [busy]="modalBusy" size="md">
  <ng-template #abpHeader>
    <h3>{{ 'AbpIdentity::SetPassword' | abpLocalization }}</h3>
  </ng-template>

  <ng-template #abpBody>
    <form [formGroup]="setPasswordForm" (ngSubmit)="setPassword()" validateOnSubmit>
      <div class="mt-2 fade-in-top">
        <div class="form-group">
          <label for="new-password">{{ 'AbpIdentity::Password' | abpLocalization }}</label>
          <div class="input-group">
            <div class="col-10 p-0">
              <input
                type="text"
                id="new-password"
                class="form-control"
                formControlName="newPassword"
                autofocus
              />
            </div>
            <div class="input-group-append col-2 p-0">
              <button
                class="btn btn-secondary mh-35"
                id="generate-random-password-button"
                type="button"
                (click)="generatePassword()"
              >
                <i class="fa fa-refresh"></i>
              </button>
            </div>
          </div>
        </div>
      </div>
    </form>
  </ng-template>

  <ng-template #abpFooter>
    <button type="button" class="btn btn-secondary" #abpClose>
      {{ 'AbpIdentity::Cancel' | abpLocalization }}
    </button>
    <abp-button
      iconClass="fa fa-check"
      (click)="setPassword()"
      [disabled]="setPasswordForm?.invalid"
      >{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
    >
  </ng-template>
</abp-modal>
  1. Open app.component.ts in src/app folder and modify as shown below:
    import { ReplaceableComponentsService } from '@abp/ng.core'; // added this line
    import { Component } from '@angular/core';
    import { UsersComponent } from './identity-extended/users.component'; // added this line
    import { eIdentityComponents } from '@volo/abp.ng.identity'; // added this line
    
    @Component({
      selector: 'app-root',
      template: `
        <abp-loader-bar></abp-loader-bar>
        <router-outlet></router-outlet>
      `,
    })
    export class AppComponent {
      constructor(private replaceableComponentsService: ReplaceableComponentsService) { // injected ReplaceableComponentsService
      // added below
        replaceableComponentsService.add({
            component: UsersComponent,
            key: eIdentityComponents.Users
        });
      }
    }

How to disable role checkboxes

Open users.component.html and add disabled attribute to checkbox element like code below

  <li id="user-roles" ngbNavItem>
    <a ngbNavLink>{{ 'AbpIdentity::Roles' | abpLocalization }}</a>
    <ng-template ngbNavContent>
      <div
        *ngFor="let roleGroup of roleGroups; let i = index; trackBy: trackByFn"
        class="custom-checkbox custom-control mb-2"
      >
        <input
          type="checkbox"
          class="custom-control-input"
          [attr.id]="'roles-' + i"
          [formControl]="roleGroup.controls[roles[i].name]"
          [disabled]="yourDisabledLogic" <!-- This line added -->     
        />
        <label class="custom-control-label" [attr.for]="'roles-' + i">{{
          roles[i].name
        }}</label>
      </div></ng-template
    >
  </li>

How to remove organization units tab

Open users.component.html and find li element which id attribute's equal to user-organization-units. You can remove this element or you can add ngIf directive to this element.

How to modify claims action at UsersComponent

Create a file entity-action-contributors.ts and replace content with following lines:

import {eIdentityComponents, IdentityEntityActionContributors} from '@volo/abp.ng.identity';
import {EntityAction, EntityActionList} from '@abp/ng.theme.shared/extensions';
import {UsersComponent} from './users.component';

const myClaimsAction = new EntityAction<any>({
  text: 'AbpIdentity::Claims',
  action: data => {
    const component = data.getInjected(UsersComponent);
    component.onManageClaims(data.record.id);
  },
  permission: 'AbpIdentity.Users.Update',
  visible: (data) => true // your logic goes here
});

export function claimsContributor(actionList: EntityActionList<any>) {
  const index = actionList.indexOf(
    'AbpIdentity::Claims',
    (action, text) => action.text === text,
  );
  actionList.dropByIndex(index);
  actionList.addByIndex(myClaimsAction, index);
}

export const identityEntityActionContributors: IdentityEntityActionContributors = {
  // enum indicates the page to add contributors to
  [eIdentityComponents.Users]: [
    claimsContributor,
    // You can add more contributors here
  ],
};

Open IdentityExtendedModuleand add following lines:

import {identityEntityActionContributors} from "./entity-action-contributors";

@NgModule({
  declarations: [UsersComponent],
  imports: [
    IdentityModule.forChild(
      { 
        entityActionContributors: identityEntityActionContributors, // this line added
      }
    ),
    // other imports
  ]
})
export class IdentityExtendedModule{}

How to add view action

Open users.component.ts and add following lines

@Component(/*component meta*/)
export class UsersComponent {
 // other variables
  
  user: Identity.UserItem;
  isUserQuickViewVisible: boolean;
  
  // other methods
  
  openUserQuickView(record: Identity.UserItem) {
    this.user = new Proxy(record, {
      get: (target, prop) => target[prop] || '—',
    });
    this.isUserQuickViewVisible = true;
  }
}

Open users.component.html and add following lines:

<abp-modal [(visible)]="isUserQuickViewVisible">
  <ng-template #abpHeader>
    <h3>{{ user.userName }}</h3>
  </ng-template>

  <ng-template #abpBody>
    <table class="table table-borderless">
      <tbody>
      <tr>
        <th scope="row">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</th>
        <td>{{ user.name }}</td>
      </tr>
      <tr>
        <th scope="row">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</th>
        <td>{{ user.surname }}</td>
      </tr>
      <tr>
        <th scope="row">{{ 'AbpIdentity::EmailAddress' | abpLocalization }}</th>
        <td>{{ user.email }}</td>
      </tr>
      <tr>
        <th scope="row">{{ 'AbpIdentity::PhoneNumber' | abpLocalization }}</th>
        <td>{{ user.phoneNumber }}</td>
      </tr>
      </tbody>
    </table>
  </ng-template>

  <ng-template #abpFooter>
    <button type="button" class="btn btn-secondary" abpClose>
      {{ 'AbpUi::Close' | abpLocalization }}
    </button>
  </ng-template>
</abp-modal>

Open entity-action-contributors.ts and add following lines:

const quickViewAction = new EntityAction<Identity.UserItem>({
  text: 'Quick View',
  action: data => {
    const component = data.getInjected(UsersComponent);
    component.openUserQuickView(data.record);
  },
});

export function customModalContributor(actionList: EntityActionList<Identity.UserItem>) {
  actionList.addTail(quickViewAction);
}

export const identityEntityActionContributors: IdentityEntityActionContributors = {
  [eIdentityComponents.Users]: [
    claimsContributor,
    customModalContributor // this line added
  ],
};
@m7amed-hussein
Copy link

Any updates for version 5.2?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment