Convert Create/Edit Modals to Page
In this document we will explain how to convert the BookStore's Books create & edit modals to regular Angular component pages.
Before
After
BooksComponent
This is the main component of the books management. The create & update operations are done in this page. So we'll remove the create & update operations from this page and move a separate angular component for each operation. Each component will be a page.
Remove the Create & Update modal on template
Modify the NewBook button with a link to the CreateBookComponent page.
<button *abpPermission="'BookStore.Books.Create'" id="create" class="btn btn-primary" type="button" [routerLink]="['create']" > <i class="fa fa-plus me-1"></i> <span>{{ '::NewBook' | abpLocalization }}</span> </button>
Modify the Edit button with a link to the EditBookComponent page.
<button *abpPermission="'BookStore.Books.Edit'" ngbDropdownItem [routerLink]="['edit', row.id]" > {{ '::Edit' | abpLocalization }} </button>
Remove all unused methods and variables in the
BookComponent
except the book variable, the ngOnInit and delete methods.Also we can clear unncessary imports
CreateBookComponent Page
Create a new component by the name create-book
in your project.
yarn ng g c book/create-book --skip-tests --style=none
create-book.component.html
<div class="card">
<div class="card-body">
<form [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<label for="author-id">Author</label><span> * </span>
<select class="form-control" id="author-id" formControlName="authorId">
<option [ngValue]="null">Select author</option>
<option [ngValue]="author.id" *ngFor="let author of authors$ | async">
{{ author.name }}
</option>
</select>
</div>
<div class="mt-2">
<label for="book-name">Name</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="mt-2">
<label for="book-price">Price</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="mt-2">
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="type.value" *ngFor="let type of bookTypes">
{{ '::Enum:BookType.' + type.value | abpLocalization }}
</option>
</select>
</div>
<div class="mt-2">
<label>Publish date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="publishDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
<div class="pt-2">
<button type="button" class="btn btn-secondary m-1" [routerLink]="['/books']">
{{ '::Cancel' | abpLocalization }}
</button>
<button class="btn btn-primary">
<i class="fa fa-check mr-1"></i>
{{ '::Save' | abpLocalization }}
</button>
</div>
</form>
</div>
</div>
create-book.component.ts
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { map } from 'rxjs';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { BookService, bookTypeOptions } from '@proxy/books';
const { required } = Validators;
@Component({
selector: 'app-create-book',
templateUrl: './create-book.component.html',
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class CreateBookComponent {
//inject() function came with Angular v14, detail: https://angular.io/api/core/inject
private readonly router = inject(Router);
private readonly fb = inject(FormBuilder);
private readonly bookService = inject(BookService);
form: FormGroup;
authors$ = this.bookService.getAuthorLookup().pipe(map(({ items }) => items));
bookTypes = bookTypeOptions;
private buildForm() {
this.form = this.fb.group({
authorId: [null, required],
name: [null, required],
type: [null, required],
publishDate: [undefined, required],
price: [null, required],
});
}
constructor() {
this.buildForm();
}
save() {
if (this.form.invalid) return;
this.bookService.create(this.form.value).subscribe(() => {
this.router.navigate(['/books']);
});
}
}
EditBookComponent Page
Create a new component by the name edit-book in your project.
yarn ng g c book/edit-book --skip-tests --style=none
edit-book.component.html
<div class="card">
<div class="card-body">
<form *ngIf="form" [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<label for="author-id">Author</label><span> * </span>
<select class="form-control" id="author-id" formControlName="authorId">
<option [ngValue]="null">Select author</option>
<option [ngValue]="author.id" *ngFor="let author of authors$ | async">
{{ author.name }}
</option>
</select>
</div>
<div class="mt-2">
<label for="book-name">Name</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="mt-2">
<label for="book-price">Price</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="mt-2">
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="type.value" *ngFor="let type of bookTypes">
{{ '::Enum:BookType.' + type.value | abpLocalization }}
</option>
</select>
</div>
<div class="mt-2">
<label>Publish date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="publishDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
<div class="pt-2">
<button type="button" class="btn btn-secondary m-1" [routerLink]="['/books']">
{{ '::Cancel' | abpLocalization }}
</button>
<button class="btn btn-primary">
<i class="fa fa-check mr-1"></i>
{{ '::Save' | abpLocalization }}
</button>
</div>
</form>
</div>
</div>
edit-book.component.ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { filter, map, switchMap, tap } from 'rxjs';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { BookDto, BookService, bookTypeOptions } from '@proxy/books';
const { required } = Validators;
@Component({
selector: 'app-edit-book',
templateUrl: './edit-book.component.html',
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class EditBookComponent {
//inject() function came with Angular v14, detail: https://angular.io/api/core/inject
private readonly router = inject(Router);
private readonly activatedRoute = inject(ActivatedRoute);
private readonly fb = inject(FormBuilder);
private readonly bookService = inject(BookService);
id: string;
form: FormGroup;
authors$ = this.bookService.getAuthorLookup().pipe(map(({ items }) => items));
bookTypes = bookTypeOptions;
private buildForm(book: BookDto): void {
this.form = this.fb.group({
authorId: [book.authorId, required],
name: [book.name, required],
type: [book.type, required],
publishDate: [new Date(book.publishDate), required],
price: [book.price, required],
});
}
constructor() {
this.activatedRoute.params
.pipe(
filter(params => params.id),
tap(({ id }) => (this.id = id)),
switchMap(({ id }) => this.bookService.get(id)),
tap(book => this.buildForm(book))
)
.subscribe();
}
save(): void {
if (this.form.invalid) return;
this.bookService.update(this.id, this.form.value).subscribe(() => {
this.router.navigate(['/books']);
});
}
}
Update BookRoutingModule
Finally add 2 items to the routes array in the book-routing.module.ts
import { CreateBookComponent } from './create-book/create-book.component';
import { EditBookComponent } from './edit-book/edit-book.component';
const routes: Routes = [
{ path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] },
{ path: 'create', component: CreateBookComponent },
{ path: 'edit/:id', component: EditBookComponent },
];
You can check the following commit for more details: https://github.com/abpframework/abp-samples/commit/351ad5e093036702edbb15169968935496afea0e
Comments
Masum ULU 86 weeks ago
Here's the
Blazor Version: https://community.abp.io/posts/converting-createedit-modal-to-page-blazor-eexdex8y
MVC Version: https://community.abp.io/posts/converting-createedit-modal-to-page-4ps5v60m
john may 85 weeks ago
Hi Masum,
Thanks for wrting this article. I have found one issue though. Breadcrumb/Menu is changes back to "Home" when you redirected to create/edit page. Regards, John
Masum ULU 85 weeks ago
Hi John,
You're right, it's because of I forgot add items to route provider for Book (Create and Update) page. I'll fix it Thank you!
Mithunpandi 49 weeks ago
Hi, How can we do convert Create/Edit User Modals to Page in angular?