---
title: "Converting Create/Edit Modal to Page (Angular)"
description: "ASP.NET Zero is the base solution for building robust and scalable web applications. Unlock your full potential today!"
url: "https://aspnetzero.com/blog/converting-create-edit-modal-to-page-angular"
image: "https://aspnetzero.com/Images/Blog/converting-create-edit-modal-to-page-angular.png"
---

[Blog](https://aspnetzero.com/blog)

# Converting Create/Edit Modal to Page (Angular)

02 March 2022 15 min read [Angular](https://aspnetzero.com/blog/topics/angular) [How To](https://aspnetzero.com/blog/topics/how-to)

![Converting Create/Edit Modal to Page (Angular)](https://aspnetzero.com/Images/Blog/converting-create-edit-modal-to-page-angular.png)

Share [X](https://twitter.com/intent/tweet?url=https%3A%2F%2Faspnetzero.com%2Fblog%2Fconverting-create-edit-modal-to-page-angular&text=Converting+Create%2FEdit+Modal+to+Page+%28Angular%29) [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Faspnetzero.com%2Fblog%2Fconverting-create-edit-modal-to-page-angular) [Reddit](https://reddit.com/submit?url=https%3A%2F%2Faspnetzero.com%2Fblog%2Fconverting-create-edit-modal-to-page-angular&title=Converting+Create%2FEdit+Modal+to+Page+%28Angular%29) [Hacker News](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Faspnetzero.com%2Fblog%2Fconverting-create-edit-modal-to-page-angular&t=Converting+Create%2FEdit+Modal+to+Page+%28Angular%29)

# Converting Create & Edit Tenant Modal to Page in Angular

When developing applications with ASP.NET Zero, one common requirement is to **create** and **edit** tenants. Traditionally, many developers rely on **modals** to handle these operations, providing a convenient overlay for the user interface. However, there's an alternative approach that can enhance the user experience and simplify the development process: using pages instead of modals.

In this blog post, we'll provide step-by-step instructions on how to implement this technique in your own project, enabling you to make the most out of the ASP.NET Zero framework.

## Create Tenant

### Create Tenant Page Html

First, create a new html page named `create-tenant.component.html` with the content below. This is an empty page template for ASP.NET Zero's Angular pages.

```html
<div [@routerTransition]>
    <div class="kt-content  kt-grid__item kt-grid__item--fluid kt-grid kt-grid--hor">
        <div class="kt-subheader kt-grid__item">
            <div class="kt-container ">
                <div class="kt-subheader__main">
                    <h3 class="kt-subheader__title">
                        <span>{{"CreateNewTenant" | localize}}</span>
                    </h3>
                    <span class="kt-subheader__separator kt-subheader__separator--v"></span>
                    <span class="kt-subheader__desc">
                        {{"CreateTenantHeaderInfo" | localize}}
                    </span>
                </div>
            </div>
        </div>
        <div class="kt-container kt-grid__item kt-grid__item--fluid">
            <div class="kt-portlet kt-portlet--mobile">
                <div class="kt-portlet__body  kt-portlet__body--fit">
                    <p>PHONE BOOK CONTENT COMES HERE!</p>
                </div>
            </div>
        </div>
    </div>
</div>
```

After doing that, copy the form element from `create-tenant-modal.component.html` into div with `kt-portlet__body` class. Now, there are still modal related html code in our file, so we need to remove them.

First, remove the html item with `modal-header` class since we don't need it anymore.

Now, move all content of the div with class `modal-body` into the `form` tag. After that, we can remove the div with class `modal-body`.

Finally, change the class of the div which contains Save and Cancel buttons from `modal-footer` to `kt-margin-t-40`.

Here is final version of `create-tenant.component.html`:

```html
<div [@routerTransition]>
    <div class="kt-content  kt-grid__item kt-grid__item--fluid kt-grid kt-grid--hor">
        <div class="kt-subheader kt-grid__item">
            <div class="kt-container ">
                <div class="kt-subheader__main">
                    <h3 class="kt-subheader__title">
                        <span>{{"CreateNewTenant" | localize}}</span>
                    </h3>
                    <span class="kt-subheader__separator kt-subheader__separator--v"></span>
                    <span class="kt-subheader__desc">
                        {{"CreateTenantHeaderInfo" | localize}}
                    </span>
                </div>
            </div>
        </div>
        <div class="kt-container kt-grid__item kt-grid__item--fluid">
            <div class="kt-portlet kt-portlet--mobile">
                <div class="kt-portlet__body  kt-portlet__body--fit">
                    <form #tenantCreateForm="ngForm" role="form" novalidate class="form-validation" *ngIf="active"
                        (submit)="save()">
                        <div class="form-group">
                            <label for="TenancyName">{{"TenancyName" | localize}} *</label>
                            <input id="TenancyName" #tenancyNameInput="ngModel" class="form-control" type="text"
                                [ngClass]="{'edited':tenant.tenancyName}" name="tenancyName"
                                [(ngModel)]="tenant.tenancyName" #tenancyName="ngModel" required maxlength="64"
                                pattern="^[a-zA-Z][a-zA-Z0-9_-]{1,}$">
                            <validation-messages [formCtrl]="tenancyNameInput"></validation-messages>
                        </div>
                        <div>
                            <span class="help-block text-danger"
                                *ngIf="!tenancyName.valid && !tenancyName.pristine">{{"TenantName_Regex_Description" | localize}}</span>
                        </div>

                        <div class="form-group">
                            <label for="Name">{{"TenantName" | localize}} *</label>
                            <input id="Name" #nameInput="ngModel" type="text" name="Name" class="form-control"
                                [ngClass]="{'edited':tenant.name}" [(ngModel)]="tenant.name" required maxlength="128">
                            <validation-messages [formCtrl]="nameInput"></validation-messages>
                        </div>

                        <div class="kt-checkbox-list">
                            <label class="kt-checkbox">
                                <input id="CreateTenant_UseHostDb" type="checkbox" name="UseHostDb"
                                    [(ngModel)]="useHostDb">
                                {{"UseHostDatabase" | localize}}
                                <span></span>
                            </label>
                        </div>

                        <div class="form-group" *ngIf="!useHostDb">
                            <label for="DatabaseConnectionString">{{"DatabaseConnectionString" | localize}} *</label>
                            <input id="DatabaseConnectionString" #connectionStringInput="ngModel" type="text"
                                name="ConnectionString" class="form-control" [(ngModel)]="tenant.connectionString"
                                [ngClass]="{'edited':tenant.connectionString}" required maxlength="1024">
                            <validation-messages [formCtrl]="connectionStringInput"></validation-messages>
                        </div>

                        <div class="form-group">
                            <label for="AdminEmailAddress">{{"AdminEmailAddress" | localize}} *</label>
                            <input id="AdminEmailAddress" #adminEmailAddressInput="ngModel" type="email"
                                name="AdminEmailAddress" class="form-control" [(ngModel)]="tenant.adminEmailAddress"
                                [ngClass]="{'edited':tenant.adminEmailAddress}" required
                                pattern="^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$" maxlength="256">
                            <validation-messages [formCtrl]="adminEmailAddressInput"></validation-messages>
                        </div>

                        <div class="kt-checkbox-list">
                            <label class="kt-checkbox">
                                <input id="CreateTenant_SetRandomPassword" type="checkbox" name="SetRandomPassword"
                                    [(ngModel)]="setRandomPassword">
                                {{"SetRandomPassword" | localize}}
                                <span></span>
                            </label>
                        </div>

                        <div class="form-group" *ngIf="!setRandomPassword">
                            <label for="AdminPassword">{{"AdminPassword" | localize}}</label>
                            <input id="AdminPassword" type="password" name="adminPassword" class="form-control"
                                id="adminPassword" [(ngModel)]="tenant.adminPassword"
                                [ngClass]="{'edited':tenant.adminPassword}" [required]="!setRandomPassword"
                                #adminPassword="ngModel" validateEqual="adminPasswordRepeat" reverse="true"
                                maxlength="32" [requireDigit]="passwordComplexitySetting.requireDigit"
                                [requireLowercase]="passwordComplexitySetting.requireLowercase"
                                [requireUppercase]="passwordComplexitySetting.requireUppercase"
                                [requireNonAlphanumeric]="passwordComplexitySetting.requireNonAlphanumeric"
                                [requiredLength]="passwordComplexitySetting.requiredLength">
                        </div>

                        <div [hidden]="tenantCreateForm.form.valid || tenantCreateForm.form.pristine">
                            <ul class="help-block text-danger"
                                *ngIf="tenantCreateForm.controls['adminPassword'] && tenantCreateForm.controls['adminPassword'].errors">
                                <li [hidden]="!tenantCreateForm.controls['adminPassword'].errors.requireDigit">
                                    {{"PasswordComplexity_RequireDigit_Hint" | localize}}</li>
                                <li [hidden]="!tenantCreateForm.controls['adminPassword'].errors.requireLowercase">
                                    {{"PasswordComplexity_RequireLowercase_Hint" | localize}}</li>
                                <li [hidden]="!tenantCreateForm.controls['adminPassword'].errors.requireUppercase">
                                    {{"PasswordComplexity_RequireUppercase_Hint" | localize}}</li>
                                <li
                                    [hidden]="!tenantCreateForm.controls['adminPassword'].errors.requireNonAlphanumeric">
                                    {{"PasswordComplexity_RequireNonAlphanumeric_Hint" | localize}}</li>
                                <li [hidden]="!tenantCreateForm.controls['adminPassword'].errors.requiredLength">
                                    {{"PasswordComplexity_RequiredLength_Hint" | localize:passwordComplexitySetting.requiredLength}}
                                </li>
                            </ul>
                        </div>

                        <div class="form-group" *ngIf="!setRandomPassword">
                            <label for="AdminPasswordRepeat">{{"AdminPasswordRepeat" | localize}}</label>
                            <input id="AdminPasswordRepeat" type="password" name="adminPasswordRepeat"
                                class="form-control" [(ngModel)]="tenant.adminPasswordRepeat"
                                [ngClass]="{'edited':tenant.adminPasswordRepeat}" [required]="!setRandomPassword"
                                #adminPasswordRepeat="ngModel" [requireDigit]="passwordComplexitySetting.requireDigit"
                                [requireLowercase]="passwordComplexitySetting.requireLowercase"
                                [requireUppercase]="passwordComplexitySetting.requireUppercase"
                                [requireNonAlphanumeric]="passwordComplexitySetting.requireNonAlphanumeric"
                                [requiredLength]="passwordComplexitySetting.requiredLength"
                                validateEqual="adminPassword" maxlength="32">
                        </div>

                        <div [hidden]="tenantCreateForm.form.valid || tenantCreateForm.form.pristine">
                            <ul class="help-block text-danger"
                                *ngIf="tenantCreateForm.controls['adminPasswordRepeat'] && tenantCreateForm.controls['adminPasswordRepeat'].errors">
                                <li [hidden]="!tenantCreateForm.controls['adminPasswordRepeat'].errors.requireDigit">
                                    {{"PasswordComplexity_RequireDigit_Hint" | localize}}</li>
                                <li
                                    [hidden]="!tenantCreateForm.controls['adminPasswordRepeat'].errors.requireLowercase">
                                    {{"PasswordComplexity_RequireLowercase_Hint" | localize}}</li>
                                <li
                                    [hidden]="!tenantCreateForm.controls['adminPasswordRepeat'].errors.requireUppercase">
                                    {{"PasswordComplexity_RequireUppercase_Hint" | localize}}</li>
                                <li
                                    [hidden]="!tenantCreateForm.controls['adminPasswordRepeat'].errors.requireNonAlphanumeric">
                                    {{"PasswordComplexity_RequireNonAlphanumeric_Hint" | localize}}</li>
                                <li [hidden]="!tenantCreateForm.controls['adminPasswordRepeat'].errors.requiredLength">
                                    {{"PasswordComplexity_RequiredLength_Hint" | localize:passwordComplexitySetting.requiredLength}}
                                </li>
                                <li [hidden]="tenantCreateForm.controls['adminPasswordRepeat'].valid">
                                    {{"PasswordsDontMatch" | localize}}</li>
                            </ul>
                        </div>

                        <div class="form-group">
                            <label for="edition">{{"Edition" | localize}}</label>
                            <select id="edition" name="edition" class="form-control" [(ngModel)]="tenant.editionId"
                                (change)="onEditionChange($event)">
                                <option *ngFor="let edition of editions" [value]="edition.value">{{edition.displayText}}
                                </option>
                            </select>
                        </div>

                        <div [hidden]="!isSubscriptionFieldsVisible" class="kt-checkbox-list">
                            <label for="CreateTenant_IsUnlimited" class="kt-checkbox">
                                <input id="CreateTenant_IsUnlimited" type="checkbox" name="IsUnlimited"
                                    [(ngModel)]="isUnlimited" />
                                {{"UnlimitedTimeSubscription" | localize}}
                                <span></span>
                            </label>
                        </div>

                        <div [hidden]="isUnlimited || !isSubscriptionFieldsVisible" class="form-group"
                            [ngClass]="{'has-error': !subscriptionEndDateIsValid()}">
                            <label for="SubscriptionEndDate">{{"SubscriptionEndDate" | localize}}</label>
                            <input id="SubscriptionEndDate" type="text" #SubscriptionEndDateUtc
                                name="SubscriptionEndDateUtc" class="form-control" bsDatepicker
                                [(ngModel)]="tenant.subscriptionEndDateUtc" autocomplete="off">
                        </div>

                        <div [hidden]="!isSubscriptionFieldsVisible" class="kt-checkbox-list">
                            <label for="CreateTenant_IsInTrialPeriod" class="kt-checkbox">
                                <input id="CreateTenant_IsInTrialPeriod" type="checkbox" name="IsInTrialPeriod"
                                    [disabled]="isSelectedEditionFree" [(ngModel)]="tenant.isInTrialPeriod">
                                {{"IsInTrialPeriod" | localize}}
                                <span></span>
                            </label>
                        </div>

                        <div class="kt-checkbox-list">
                            <label for="CreateTenant_ShouldChangePasswordOnNextLogin" class="kt-checkbox">
                                <input id="CreateTenant_ShouldChangePasswordOnNextLogin" type="checkbox"
                                    name="ShouldChangePasswordOnNextLogin"
                                    [(ngModel)]="tenant.shouldChangePasswordOnNextLogin">
                                {{"ShouldChangePasswordOnNextLogin" | localize}}
                                <span></span>
                            </label>
                            <label for="CreateTenant_SendActivationEmail" class="kt-checkbox">
                                <input id="CreateTenant_SendActivationEmail" type="checkbox" name="SendActivationEmail"
                                    [(ngModel)]="tenant.sendActivationEmail">
                                {{"SendActivationEmail" | localize}}
                                <span></span>
                            </label>
                            <label for="CreateTenant_IsActive" class="kt-checkbox">
                                <input id="CreateTenant_IsActive" type="checkbox" name="IsActive"
                                    [(ngModel)]="tenant.isActive">
                                {{"Active" | localize}}
                                <span></span>
                            </label>
                        </div>
                        <div class="kt-margin-t-40">
                            <button type="button" [disabled]="saving" class="btn btn-secondary"
                                (click)="close()">{{"Cancel" | localize}}</button>
                            <button type="submit" [buttonBusy]="saving" [busyText]="l('SavingWithThreeDot')"
                                class="btn btn-primary"
                                [disabled]="!tenantCreateForm.form.valid || saving || !subscriptionEndDateIsValid()"><i
                                    class="fa fa-save"></i> <span>{{"Save" | localize}}</span></button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
```

### Create Tenant Page .ts

Create a new file next to html file we have just created using the name `create-tenant.component.ts`. Copy the content of `create-tenant-modal.component.ts` to newly created file. Change the component name from `CreateTenantModalComponent` to `CreateTenantComponent`.

Modal page uses two methods named `show` and `onShown` which are not available in regular pages. So, we will implement `OnInit` and `AfterViewInit` in our Angular component and move the code of show method into `ngOnInit` and move the code of `onShown` into `ngAfterViewInit`. After doing that, we can delete empty `show` and `onShown` methods.

Import `OnInit` and `AfterViewInit` and move lines from `show` to `ngOnInit` and move lines from `onShown` to `ngAfterViewInit`.

Then, change the value of `templateUrl` from `./create-tenant-modal.component.html` to `./create-tenant.component.html`. You can also delete selector property of component definition since it is not mandatory.

Since we are using animation when routing to create tenant page, import `appModuleAnimation` into the component and use it as the value for `animations` of the component definition.

```ts
// other imports.
import { appModuleAnimation } from '@shared/animations/routerTransition';

@Component({
    templateUrl: './create-tenant.component.html',
    animations: [appModuleAnimation()]
})
// component definition.
```

Also delete `modal` and `modalSave` properties since we don't need them anymore. Also, delete their usages in the component.

When the tenant is created, instead of closing the modal, we need to redirect user to tenant list. In order to do that, inject `Router` into our component and add below line into the close method of the component.

`this._router.navigate(['app/admin/tenants']);`

After all, you can delete `create-tenant-modal.component.ts` from your project.

Here is final version of `create-tenant.component.ts`:

```ts
import { Component, EventEmitter, Injector, Output, ViewChild, OnInit, AfterViewInit } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import {
    CommonLookupServiceProxy, CreateTenantInput,
    PasswordComplexitySetting, ProfileServiceProxy,
    TenantServiceProxy, SubscribableEditionComboboxItemDto
} from '@shared/service-proxies/service-proxies';
import { filter as _filter } from 'lodash-es';
import { ModalDirective } from 'ngx-bootstrap';
import { finalize } from 'rxjs/operators';
import { appModuleAnimation } from '@shared/animations/routerTransition';
import { Router } from '@angular/router';

@Component({
    templateUrl: './create-tenant.component.html',
    animations: [appModuleAnimation()]
})
export class CreateTenantComponent extends AppComponentBase implements OnInit, AfterViewInit {

    active = false;
    saving = false;
    setRandomPassword = true;
    useHostDb = true;
    editions: SubscribableEditionComboboxItemDto[] = [];
    tenant: CreateTenantInput;
    passwordComplexitySetting: PasswordComplexitySetting = new PasswordComplexitySetting();
    isUnlimited = false;
    isSubscriptionFieldsVisible = false;
    isSelectedEditionFree = false;

    constructor(
        injector: Injector,
        private _tenantService: TenantServiceProxy,
        private _commonLookupService: CommonLookupServiceProxy,
        private _profileService: ProfileServiceProxy,
        private _router: Router
    ) {
        super(injector);
    }

    ngAfterViewInit(): void {
        document.getElementById('TenancyName').focus();
    }

    ngOnInit(): void {
        this.active = true;
        this.init();

        this._profileService.getPasswordComplexitySetting().subscribe(result => {
            this.passwordComplexitySetting = result.setting;
        });
    }

    init(): void {
        this.tenant = new CreateTenantInput();
        this.tenant.isActive = true;
        this.tenant.shouldChangePasswordOnNextLogin = true;
        this.tenant.sendActivationEmail = true;
        this.tenant.editionId = 0;
        this.tenant.isInTrialPeriod = false;

        this._commonLookupService.getEditionsForCombobox(false)
            .subscribe((result) => {
                this.editions = result.items;

                let notAssignedItem = new SubscribableEditionComboboxItemDto();
                notAssignedItem.value = '';
                notAssignedItem.displayText = this.l('NotAssigned');

                this.editions.unshift(notAssignedItem);

                this._commonLookupService.getDefaultEditionName().subscribe((getDefaultEditionResult) => {
                    let defaultEdition = _filter(this.editions, { 'displayText': getDefaultEditionResult.name });
                    if (defaultEdition && defaultEdition[0]) {
                        this.tenant.editionId = parseInt(defaultEdition[0].value);
                        this.toggleSubscriptionFields();
                    }
                });
            });
    }

    getEditionValue(item): number {
        return parseInt(item.value);
    }

    selectedEditionIsFree(): boolean {
        let selectedEditions = _filter(this.editions, { 'value': this.tenant.editionId.toString() })
            .map(u => Object.assign(new SubscribableEditionComboboxItemDto(), u));

        if (selectedEditions.length !== 1) {
            this.isSelectedEditionFree = true;
        }

        let selectedEdition = selectedEditions[0];
        this.isSelectedEditionFree = selectedEdition.isFree;
        return this.isSelectedEditionFree;
    }

    subscriptionEndDateIsValid(): boolean {
        if (this.tenant.editionId <= 0) {
            return true;
        }

        if (this.isUnlimited) {
            return true;
        }

        if (!this.tenant.subscriptionEndDateUtc) {
            return false;
        }

        return this.tenant.subscriptionEndDateUtc !== undefined;
    }

    save(): void {
        this.saving = true;

        if (this.setRandomPassword) {
            this.tenant.adminPassword = null;
        }

        if (this.tenant.editionId === 0) {
            this.tenant.editionId = null;
        }

        if (this.isUnlimited || this.tenant.editionId <= 0) {
            this.tenant.subscriptionEndDateUtc = null;
        }

        this._tenantService.createTenant(this.tenant)
            .pipe(finalize(() => this.saving = false))
            .subscribe(() => {
                this.notify.info(this.l('SavedSuccessfully'));
                this.close();
            });
    }

    close(): void {
        this.active = false;
        this._router.navigate(['app/admin/tenants']);
    }

    onEditionChange(): void {
        this.tenant.isInTrialPeriod = this.tenant.editionId > 0 && !this.selectedEditionIsFree();
        this.toggleSubscriptionFields();
    }

    toggleSubscriptionFields() {
        this.isSelectedEditionFree = this.selectedEditionIsFree();
        if (this.tenant.editionId <= 0 || this.isSelectedEditionFree) {
            this.isSubscriptionFieldsVisible = false;

            if (this.isSelectedEditionFree) {
                this.isUnlimited = true;
            } else {
                this.isUnlimited = false;
            }
        } else {
            this.isSubscriptionFieldsVisible = true;
        }
    }
}
```

### Add Route for Tenant Creation Page

In order to navigate to create page, we need to add route to our route config. Add following path to `admin-routing.module.ts` for create tenant page.

```ts
...

import { CreateTenantComponent } from './tenants/create-tenant.component';

...

@NgModule({
    imports: [
        RouterModule.forChild([
            {
                path: '',
                children: [

                    ...

                    { path: 'tenants/create-tenant', component: CreateTenantComponent, data: { permission: 'Pages.Tenants.Create' } },

                    ...

                ]
            }
        ])
    ],
    exports: [
        RouterModule
    ]
})
...
```

### Remove `CreateTenantModal` Component

Remove following line from `tenants.component.html`.

```html
<createTenantModal #createTenantModal (modalSave)="getTenants()"></createTenantModal>
```

And following lines from `tenants.component.ts`

```ts
@ViewChild('createTenantModal') createTenantModal: CreateTenantModalComponent;
```

### Add `CreateTenantComponent` to `AdminModule`

In order to uses newly created `CreateTenantComponent`, we need to add it to our `AdminModule`.

Replace

`import { CreateTenantModalComponent } from './tenants/create-tenant-modal.component';`

with

`import { CreateTenantComponent } from './tenants/create-tenant.component';`

and use `CreateTenantComponent` instead of `CreateTenantModalComponent` in the imports list of AdminModule.

### Navigate to the Tenant Creation Page

Import router into `tenants.component.ts`;

```ts
import { ActivatedRoute, Router } from '@angular/router';

constructor(
        ...
        private _router: Router,
        ...
    ) {
        ...
    }
```

And navigate to tenant creation page.

```ts
createTenant(): void {
        this._router.navigate(['app/admin/tenants/create-tenant']);
}
```

## Edit Tenant

### Edit Tenant Page Html

First, create a new html page named `edit-tenant.component.html` with the content below.

```html
<div [@routerTransition]>
    <div class="kt-content  kt-grid__item kt-grid__item--fluid kt-grid kt-grid--hor">
        <div class="kt-subheader kt-grid__item">
            <div class="kt-container ">
                <div class="kt-subheader__main">
                    <h3 class="kt-subheader__title">
                        <span>{{"EditTenant" | localize}}: {{tenant.tenancyName}}</span>
                    </h3>
                    <span class="kt-subheader__separator kt-subheader__separator--v"></span>
                    <span class="kt-subheader__desc">
                        {{"EditTenantHeaderInfo" | localize}}
                    </span>
                </div>
            </div>
        </div>
        <div class="kt-container kt-grid__item kt-grid__item--fluid">
            <div class="kt-portlet kt-portlet--mobile">
                <div class="kt-portlet__body  kt-portlet__body--fit">
                </div>
            </div>
        </div>
    </div>
</div>
```

After doing that, copy the form element from `edit-tenant-modal.component.html` into div with `kt-portlet__body` class. Now, there are still modal related html code in our file, so we need to remove them.

First, remove the html item with `modal-header` class since we don't need it anymore.

Now, move all content of the div with class `modal-body` into the `form` tag. After that, we can remove the div with class `modal-body`.

Finally, change the class of the div which contains Save and Cancel buttons from `modal-footer` to `kt-margin-t-40`.

Here is final version of `edit-tenant.component.html`:

```html
<div [@routerTransition]>
    <div class="kt-content  kt-grid__item kt-grid__item--fluid kt-grid kt-grid--hor">
        <div class="kt-subheader kt-grid__item">
            <div class="kt-container ">
                <div class="kt-subheader__main">
                    <h3 class="kt-subheader__title">
                        <span>{{"EditTenant" | localize}}: {{tenant.tenancyName}}</span>
                    </h3>
                    <span class="kt-subheader__separator kt-subheader__separator--v"></span>
                    <span class="kt-subheader__desc">
                        {{"EditTenantHeaderInfo" | localize}}
                    </span>
                </div>
            </div>
        </div>
        <div class="kt-container kt-grid__item kt-grid__item--fluid">
            <div class="kt-portlet kt-portlet--mobile">
                <div class="kt-portlet__body  kt-portlet__body--fit">
                    <form #tenantEditForm="ngForm" role="form" novalidate class="form-validation" (submit)="save()"
                        *ngIf="tenant && active">
                        <div class="form-group">
                            <label for="Name">{{"TenantName" | localize}} *</label>
                            <input id="Name" #nameInput="ngModel" type="text" name="Name" class="form-control"
                                [ngClass]="{'edited':tenant.name}" [(ngModel)]="tenant.name" required maxlength="128">
                            <validation-messages [formCtrl]="nameInput"></validation-messages>
                        </div>
                        <div class="form-group" *ngIf="currentConnectionString">
                            <label for="DatabaseConnectionString">{{"DatabaseConnectionString" | localize}} *</label>
                            <input id="DatabaseConnectionString" #connectionStringInput="ngModel" type="text"
                                name="ConnectionString" class="form-control" [(ngModel)]="tenant.connectionString"
                                required maxlength="1024">
                            <validation-messages [formCtrl]="connectionStringInput"></validation-messages>
                        </div>
                        <div *ngIf="currentConnectionString">
                            <span
                                class="help-block text-warning">{{"TenantDatabaseConnectionStringChangeWarningMessage" | localize}}</span>
                        </div>
                        <div class="form-group">
                            <label for="edition">{{"Edition" | localize}}</label>
                            <select id="edition" name="edition" class="form-control" [(ngModel)]="tenant.editionId"
                                (change)="onEditionChange($event)">
                                <option *ngFor="let edition of editions" [value]="edition.value">{{edition.displayText}}
                                </option>
                            </select>
                        </div>
                        <div [hidden]="!isSubscriptionFieldsVisible" class="kt-checkbox-list">
                            <label class="kt-checkbox">
                                <input id="CreateTenant_IsUnlimited" type="checkbox" name="IsUnlimited"
                                    [(ngModel)]="isUnlimited" (ngModelChange)="onUnlimitedChange()" />
                                {{"UnlimitedTimeSubscription" | localize}}
                                <span></span>
                            </label>
                        </div>
                        <div [hidden]="isUnlimited || !isSubscriptionFieldsVisible" class="form-group"
                            [ngClass]="{'has-error': !subscriptionEndDateUtcIsValid }">
                            <label for="SubscriptionEndDateUtc">{{"SubscriptionEndDateUtc" | localize}}</label>
                            <input id="SubscriptionEndDateUtc" type="datetime" #SubscriptionEndDateUtc
                                name="SubscriptionEndDateUtc" class="form-control"
                                [ngClass]="{'edited':tenant.subscriptionEndDateUtc}"
                                (bsValueChange)="subscriptionEndDateChange($event)" bsDatepicker
                                [(ngModel)]="tenant.subscriptionEndDateUtc" [required]="!isUnlimited">
                        </div>
                        <div [hidden]="!isSubscriptionFieldsVisible" class="kt-checkbox-list">
                            <label class="kt-checkbox">
                                <input id="CreateTenant_IsInTrialPeriod" type="checkbox" name="IsInTrialPeriod"
                                    [disabled]="selectedEditionIsFree()" [(ngModel)]="tenant.isInTrialPeriod">
                                {{"IsInTrialPeriod" | localize}}
                                <span></span>
                            </label>
                        </div>
                        <div class="kt-checkbox-list">
                            <label class="kt-checkbox">
                                <input id="EditTenant_IsActive" type="checkbox" name="IsActive"
                                    [(ngModel)]="tenant.isActive">
                                {{"Active" | localize}}
                                <span></span>
                            </label>
                        </div>
                        <div class="kt-margin-t-40">
                            <button type="button" [disabled]="saving" class="btn btn-secondary"
                                (click)="close()">{{"Cancel" | localize}}</button>
                            <button type="submit" [buttonBusy]="saving" [busyText]="l('SavingWithThreeDot')"
                                class="btn btn-primary"
                                [disabled]="!tenantEditForm.form.valid || saving || !subscriptionEndDateUtcIsValid"><i
                                    class="fa fa-save"></i> <span>{{"Save" | localize}}</span></button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
```

### Edit tenant Page ts

Create a new file next to html file we have just created using the name `edit-tenant.component.ts`. Copy the content of `edit-tenant-modal.component.ts` to newly created file. Change the component name from `EditTenantModalComponent` to `EditTenantComponent`.

Modal page uses two methods named `show` and `onShown` which are not available in regular pages. So, we will implement `OnInit` and `AfterViewInit` in our Angular component and move the code of show method into `ngOnInit` and move the code of `onShown` into `ngAfterViewInit`. After doing that, we can delete empty `show` and `onShown` methods.

Import `OnInit` and `AfterViewInit` and move lines from `show` to `ngOnInit` and move lines from `onShown` to `ngAfterViewInit`.

Then, change the value of `templateUrl` from `./edit-tenant-modal.component.html` to `./edit-tenant.component.html`. You can also delete selector property of component definition since it is not mandatory.

Since we are using animation when routing to create tenant page, import `appModuleAnimation` into the component and use it as the value for `animations` of the component definition.

```ts
// other imports.
import { appModuleAnimation } from '@shared/animations/routerTransition';

@Component({
    templateUrl: './create-tenant.component.html',
    animations: [appModuleAnimation()]
})
// component definition.
```

`EditTenantModalComponent` was getting tenantId as a parameter of it's show method. We will be getting tenantId parameter from query string. In order to do this, first inject `ActivatedRoute` into `EditTenantComponent` like below;

```typescript
// other imports
import { ActivatedRoute } from '@angular/router';

@Component({
    templateUrl: './edit-tenant-modal.component.html',
    animations: [appModuleAnimation()]
})
export class EditTenantComponent extends AppComponentBase implements OnInit, AfterViewInit {

	// other code blocks...
    
    constructor(
        injector: Injector,
        private _tenantService: TenantServiceProxy,
        private _commonLookupService: CommonLookupServiceProxy,
        private _activatedRoute: ActivatedRoute
    ) {
        super(injector);
    }
	
	// other code blocks...
}	
```

and get it's value from query string in the ngOnInit like below;

```ts
var tenantId = this._activatedRoute.snapshot.queryParams['tenantId'];
```

Also delete tenantId parameter from ngOnInit method since we are now getting it from query parameters.

Also delete `modal` and `modalSave` properties since we don't need them anymore. Also, delete their usages in the component.

When the tenant is edited, instead of closing the modal, we need to redirect user to tenant list. In order to do that, inject `Router` into our component and add below line into the close method of the component.

`this._router.navigate(['app/admin/tenants']);`

After all, you can delete `edit-tenant-modal.component.ts` from your project.

Here is final version of `edit-tenant.component.ts`:

```ts
import { Component, ElementRef, EventEmitter, Injector, Output, ViewChild, OnInit, AfterViewInit } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import { CommonLookupServiceProxy, SubscribableEditionComboboxItemDto, TenantEditDto, TenantServiceProxy } from '@shared/service-proxies/service-proxies';
import { filter as _filter } from 'lodash-es';;
import { DateTime } from 'luxon';
import { ModalDirective } from 'ngx-bootstrap';
import { finalize } from 'rxjs/operators';
import { appModuleAnimation } from '@shared/animations/routerTransition';
import { ActivatedRoute, Router } from '@angular/router';
import { DateTimeService } from '@app/shared/common/timing/date-time.service';

@Component({
    templateUrl: './edit-tenant.component.html',
    animations: [appModuleAnimation()]
})
export class EditTenantComponent extends AppComponentBase implements OnInit, AfterViewInit {

    @ViewChild('nameInput') nameInput: ElementRef;
    @ViewChild('SubscriptionEndDateUtc') subscriptionEndDateUtc: ElementRef;

    active = false;
    saving = false;
    isUnlimited = false;
    subscriptionEndDateUtcIsValid = false;
    subscriptionEndDateUtcx: DateTime;

    tenant: TenantEditDto = undefined;
    currentConnectionString: string;
    editions: SubscribableEditionComboboxItemDto[] = [];
    isSubscriptionFieldsVisible = false;

    constructor(
        injector: Injector,
        private _tenantService: TenantServiceProxy,
        private _commonLookupService: CommonLookupServiceProxy,
        private _activatedRoute: ActivatedRoute,
        private _router: Router,
        private _dateTimeService: DateTimeService
    ) {
        super(injector);
    }

    ngOnInit(): void {
        this.subscriptionEndDateUtcx = _dateTimeService.getStartOfDay();
        
        this.active = true;

        var tenantId = this._activatedRoute.snapshot.queryParams['tenantId'];
        this._commonLookupService.getEditionsForCombobox(false).subscribe(editionsResult => {
            this.editions = editionsResult.items;
            let notSelectedEdition = new SubscribableEditionComboboxItemDto();
            notSelectedEdition.displayText = this.l('NotAssigned');
            notSelectedEdition.value = '';
            this.editions.unshift(notSelectedEdition);

            this._tenantService.getTenantForEdit(tenantId).subscribe((tenantResult) => {
                this.tenant = tenantResult;
                this.currentConnectionString = tenantResult.connectionString;
                this.tenant.editionId = this.tenant.editionId || 0;
                this.isUnlimited = !this.tenant.subscriptionEndDateUtc;
                this.subscriptionEndDateUtcIsValid = this.isUnlimited || this.tenant.subscriptionEndDateUtc !== undefined;
                this.toggleSubscriptionFields();
            });
        });
    }

    ngAfterViewInit(): void {
        document.getElementById('Name').focus();

        if (this.tenant.subscriptionEndDateUtc) {
            (this.subscriptionEndDateUtc.nativeElement as any).value = this.tenant.subscriptionEndDateUtc.format('L');
        }
    }

    subscriptionEndDateChange(e): void {
        this.subscriptionEndDateUtcIsValid = e && e.date !== false || this.isUnlimited;
    }

    selectedEditionIsFree(): boolean {
        if (!this.tenant.editionId) {
            return true;
        }

        let selectedEditions = _filter(this.editions, { value: this.tenant.editionId + '' });
        if (selectedEditions.length !== 1) {
            return true;
        }

        let selectedEdition = selectedEditions[0];
        return selectedEdition.isFree;
    }

    save(): void {
        this.saving = true;
        if (this.tenant.editionId === 0) {
            this.tenant.editionId = null;
        }

        //take selected date as UTC
        if (this.isUnlimited || !this.tenant.editionId) {
            this.tenant.subscriptionEndDateUtc = null;
        }

        this._tenantService.updateTenant(this.tenant)
            .pipe(finalize(() => this.saving = false))
            .subscribe(() => {
                this.notify.info(this.l('SavedSuccessfully'));
                this.close();
            });
    }

    close(): void {
        this.active = false;
        this._router.navigate(['app/admin/tenants']);
    }

    onEditionChange(): void {
        if (this.selectedEditionIsFree()) {
            this.tenant.isInTrialPeriod = false;
        }

        this.toggleSubscriptionFields();
    }

    onUnlimitedChange(): void {
        if (this.isUnlimited) {
            this.tenant.subscriptionEndDateUtc = null;
            this.subscriptionEndDateUtcIsValid = true;
        } else {
            if (!this.tenant.subscriptionEndDateUtc) {
                this.subscriptionEndDateUtcIsValid = false;
            }
        }
    }

    toggleSubscriptionFields() {
        if (this.tenant.editionId > 0) {
            this.isSubscriptionFieldsVisible = true;
        } else {
            this.isSubscriptionFieldsVisible = false;
        }
    }
}
```

### Add route for Tenant Edit Page

Add following path to `admin-routing.module.ts` for edit tenant page.

```ts

...

import { EditTenantComponent } from './tenants/edit-tenant.component';

...

@NgModule({
    imports: [
        RouterModule.forChild([
            {
                path: '',
                children: [

                    ...

                    { path: 'tenants/edit-tenant', component: EditTenantComponent, data: { permission: 'Pages.Tenants.Edit' } },

                    ...

                ]
            }
        ])
    ],
    exports: [
        RouterModule
    ]
})
...
```

### Remove `EditTenantModal` Component

Remove following line `tenants.component.html`.

```html
<editTenantModal #editTenantModal (modalSave)="getTenants()"></editTenantModal>
```

Remove following lines from `tenants.component.ts`;

```typescript
import { EditTenantModalComponent } from './edit-tenant-modal.component';
```

```typescript
@ViewChild('editTenantModal') editTenantModal: EditTenantModalComponent;
```

### Add `CreateTenantComponent` to `AdminModule`

In order to uses newly created `EditTenantComponent`, we need to add it to our `AdminModule`.

Replace

`import { EditTenantModalComponent } from './tenants/edit-tenant-modal.component';`

with

`import { EditTenantComponent } from './tenants/edit-tenant.component';`

and use `EditTenantComponent` instead of `EditTenantModalComponent` in the imports list of AdminModule.

### Navigate to the Tenant Edit Page

Router was already injected into TenantsComponent when converting create modal to a page. We can use it to navigate to tenant edit page. In order to do that, create a new method named `editTenant` in tenants.component.ts like below;

```ts
editTenant(tenantId): void {
        this._router.navigate(['app/admin/tenants/edit-tenant'], { queryParams: { tenantId: tenantId } });
    }
```

And use it in tenants.component.html like below instead of `editTenantModal.show(record.id)`;

```typescript
<a href="javascript:;" *ngIf="permission.isGranted('Pages.Tenants.Edit')" 
(click)="editTenant(record.id)">{{'Edit' | localize}}</a>
```

That's all. Now we can run the project, open tenant list and **create** a new **tenant** or **edit** an existing **tenant** in a new **page** **instead** of **modal** window.

## See the full documentation

Explore detailed guides, API references, and step-by-step tutorials for ASP.NET Zero.

[Browse documentation](https://aspnetzero.com/documents)

## Have questions before purchasing?

Schedule a free consultation with our team to find the right plan for your project.

[Schedule a meeting](https://aspnetzero.com/schedule-meeting)

## You might also like

### [Business Benefits of Using ASP.NET Zero for Enterprise Software Development](https://aspnetzero.com/blog/business-benefits-of-aspnetzero)

11 September 2026

### [Common Mistakes and Errors When Hosting ASP.NET Core Apps](https://aspnetzero.com/blog/common-mistakes-and-errors-hosting-aspnet-core-apps)

26 August 2026

### [Using AutoMapper After Migrating to Mapperly](https://aspnetzero.com/blog/using-automapper-after-migrating-to-mapperly)

04 August 2026

## Recent posts

### [Business Benefits of Using ASP.NET Zero for Enterprise Software Development](https://aspnetzero.com/blog/business-benefits-of-aspnetzero)

11 September 2026

### [Common Mistakes and Errors When Hosting ASP.NET Core Apps](https://aspnetzero.com/blog/common-mistakes-and-errors-hosting-aspnet-core-apps)

26 August 2026

### [Using AutoMapper After Migrating to Mapperly](https://aspnetzero.com/blog/using-automapper-after-migrating-to-mapperly)

04 August 2026

## Start building with ASP.NET Zero

Full source code, premium support, Angular, React & MVC UI options.

[View pricing](https://aspnetzero.com/pricing)

## Topics
