mirror of https://github.com/abpframework/abp.git
csharpabpc-sharpframeworkblazoraspnet-coredotnet-coreaspnetcorearchitecturesaasdomain-driven-designangularmulti-tenancy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
4.7 KiB
221 lines
4.7 KiB
---
|
|
description: "ABP Angular UI patterns and best practices"
|
|
globs: "**/angular/**/*.ts,**/angular/**/*.html,**/*.component.ts"
|
|
alwaysApply: false
|
|
---
|
|
|
|
# ABP Angular UI
|
|
|
|
> **Docs**: https://abp.io/docs/latest/framework/ui/angular/overview
|
|
|
|
## Project Structure
|
|
```
|
|
src/app/
|
|
├── proxy/ # Auto-generated service proxies
|
|
├── shared/ # Shared components, pipes, directives
|
|
├── book/ # Feature module
|
|
│ ├── book.module.ts
|
|
│ ├── book-routing.module.ts
|
|
│ ├── book-list/
|
|
│ │ ├── book-list.component.ts
|
|
│ │ ├── book-list.component.html
|
|
│ │ └── book-list.component.scss
|
|
│ └── book-detail/
|
|
```
|
|
|
|
## Generate Service Proxies
|
|
```bash
|
|
abp generate-proxy -t ng
|
|
```
|
|
|
|
This generates typed service classes in `src/app/proxy/`.
|
|
|
|
## List Component Pattern
|
|
```typescript
|
|
@Component({
|
|
selector: 'app-book-list',
|
|
templateUrl: './book-list.component.html'
|
|
})
|
|
export class BookListComponent implements OnInit {
|
|
books = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
|
|
|
|
constructor(
|
|
public readonly list: ListService,
|
|
private bookService: BookService,
|
|
private confirmation: ConfirmationService
|
|
) {}
|
|
|
|
ngOnInit(): void {
|
|
this.hookToQuery();
|
|
}
|
|
|
|
private hookToQuery(): void {
|
|
this.list.hookToQuery(query =>
|
|
this.bookService.getList(query)
|
|
).subscribe(response => {
|
|
this.books = response;
|
|
});
|
|
}
|
|
|
|
create(): void {
|
|
// Open create modal
|
|
}
|
|
|
|
delete(book: BookDto): void {
|
|
this.confirmation
|
|
.warn('::AreYouSureToDelete', '::AreYouSure')
|
|
.subscribe(status => {
|
|
if (status === Confirmation.Status.confirm) {
|
|
this.bookService.delete(book.id).subscribe(() => this.list.get());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
## Localization
|
|
```typescript
|
|
// In component
|
|
constructor(private localizationService: LocalizationService) {}
|
|
|
|
getText(): string {
|
|
return this.localizationService.instant('::Books');
|
|
}
|
|
```
|
|
|
|
```html
|
|
<!-- In template -->
|
|
<h1>{{ '::Books' | abpLocalization }}</h1>
|
|
|
|
<!-- With parameters -->
|
|
<p>{{ '::WelcomeMessage' | abpLocalization: userName }}</p>
|
|
```
|
|
|
|
## Authorization
|
|
|
|
### Permission Directive
|
|
```html
|
|
<button *abpPermission="'BookStore.Books.Create'">Create</button>
|
|
```
|
|
|
|
### Permission Guard
|
|
```typescript
|
|
const routes: Routes = [
|
|
{
|
|
path: '',
|
|
component: BookListComponent,
|
|
canActivate: [PermissionGuard],
|
|
data: {
|
|
requiredPolicy: 'BookStore.Books'
|
|
}
|
|
}
|
|
];
|
|
```
|
|
|
|
### Programmatic Check
|
|
```typescript
|
|
constructor(private permissionService: PermissionService) {}
|
|
|
|
canCreate(): boolean {
|
|
return this.permissionService.getGrantedPolicy('BookStore.Books.Create');
|
|
}
|
|
```
|
|
|
|
## Forms with Validation
|
|
```typescript
|
|
@Component({...})
|
|
export class BookFormComponent {
|
|
form: FormGroup;
|
|
|
|
constructor(private fb: FormBuilder) {
|
|
this.buildForm();
|
|
}
|
|
|
|
buildForm(): void {
|
|
this.form = this.fb.group({
|
|
name: ['', [Validators.required, Validators.maxLength(128)]],
|
|
price: [0, [Validators.required, Validators.min(0)]]
|
|
});
|
|
}
|
|
|
|
save(): void {
|
|
if (this.form.invalid) return;
|
|
|
|
this.bookService.create(this.form.value).subscribe(() => {
|
|
// Handle success
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
```html
|
|
<form [formGroup]="form" (ngSubmit)="save()">
|
|
<div class="form-group">
|
|
<label for="name">{{ '::Name' | abpLocalization }}</label>
|
|
<input type="text" id="name" formControlName="name" class="form-control" />
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary" [disabled]="form.invalid">
|
|
{{ '::Save' | abpLocalization }}
|
|
</button>
|
|
</form>
|
|
```
|
|
|
|
## Configuration API
|
|
```typescript
|
|
constructor(private configService: ConfigStateService) {}
|
|
|
|
getCurrentUser(): CurrentUserDto {
|
|
return this.configService.getOne('currentUser');
|
|
}
|
|
|
|
getSettings(): void {
|
|
const setting = this.configService.getSetting('MyApp.MaxItemCount');
|
|
}
|
|
```
|
|
|
|
## Modal Service
|
|
```typescript
|
|
constructor(private modalService: ModalService) {}
|
|
|
|
openCreateModal(): void {
|
|
const modalRef = this.modalService.open(BookFormComponent, {
|
|
size: 'lg'
|
|
});
|
|
|
|
modalRef.result.then(result => {
|
|
if (result) {
|
|
this.list.get();
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
## Toast Notifications
|
|
```typescript
|
|
constructor(private toaster: ToasterService) {}
|
|
|
|
showSuccess(): void {
|
|
this.toaster.success('::BookCreatedSuccessfully', '::Success');
|
|
}
|
|
|
|
showError(error: string): void {
|
|
this.toaster.error(error, '::Error');
|
|
}
|
|
```
|
|
|
|
## Lazy Loading Modules
|
|
```typescript
|
|
// app-routing.module.ts
|
|
const routes: Routes = [
|
|
{
|
|
path: 'books',
|
|
loadChildren: () => import('./book/book.module').then(m => m.BookModule)
|
|
}
|
|
];
|
|
```
|
|
|
|
## Theme & Styling
|
|
- Use Bootstrap classes
|
|
- ABP provides theme variables via CSS custom properties
|
|
- Component-specific styles in `.component.scss`
|
|
|