--- 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; 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

{{ '::Books' | abpLocalization }}

{{ '::WelcomeMessage' | abpLocalization: userName }}

``` ## Authorization ### Permission Directive ```html ``` ### 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
``` ## 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`