• Skip to main content
  • Skip to primary sidebar

Web Development Archive

  • Archive
You are here: Home / Archives for Angular

Angular

PrimeNG Dialog with Search Field

<p-button
[dt]="headerButtonDt"
label="Keresés"
severity="secondary"
[outlined]="true"
icon="pi pi-search"
(onClick)="onSearchDialogOpen()"
/>
protected onSearchDialogOpen(): void {
this._searchService.searchDialogOpen();
}
import { SearchDialog } from '@/shared/components/search-dialog/search-dialog';
import { inject, Injectable } from '@angular/core';
import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';

@Injectable({
providedIn: 'root',
})
export class SearchService {
private readonly _dialogService = inject(DialogService);
private _dialogRef: DynamicDialogRef | null = null;

public searchDialogOpen(): void {
this._dialogRef = this._dialogService.open(SearchDialog, {
modal: true,
dismissableMask: true,
width: '420px',
data: {},
});
}

public closeDialog(): void {
this._dialogRef?.close();
}
}
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { DynamicDialogRef } from 'primeng/dynamicdialog';
import { IconFieldModule } from 'primeng/iconfield';
import { InputIconModule } from 'primeng/inputicon';
import { InputTextModule } from 'primeng/inputtext';

@Component({
selector: 'app-search-dialog',
imports: [CommonModule, ButtonModule, IconFieldModule, InputIconModule, InputTextModule],
templateUrl: './search-dialog.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchDialog {
private readonly _dialogRef = inject(DynamicDialogRef);

close(): void {
this._dialogRef.close();
}
}
<div class="card flex flex-wrap justify-center gap-4">
<p-iconfield styleClass="w-full">
<p-inputicon class="pi pi-search" />
<input type="text" pInputText placeholder="Keresés ..." class="w-full" />
</p-iconfield>
</div>
import { DialogDesignTokens } from '@primeuix/themes/types/dialog';

export const dialog: DialogDesignTokens = {
colorScheme: {
light: {
root: {
background: '{surface.0}',
color: '{surface.900}',
borderColor: '{surface.200}',
borderRadius: '1rem',
shadow: '0 8px 32px rgba(0,0,0,0.08)',
},
header: {
padding: '0',
gap: '0.75rem',
},
title: {
fontSize: '1.1rem',
fontWeight: '600',
},
content: {
padding: '1rem',
},
footer: {
padding: '1rem 1.25rem',
gap: '0.5rem',
},
},
dark: {
root: {
background: '{surface.900}',
color: '{surface.0}',
borderColor: '{surface.700}',
borderRadius: '1rem',
shadow: '0 8px 32px rgba(0,0,0,0.6)',
},
header: {
padding: '1rem 1.25rem',
gap: '0.75rem',
},
title: {
fontSize: '1.1rem',
fontWeight: '600',
},
content: {
padding: '1.25rem',
},
footer: {
padding: '1rem 1.25rem',
gap: '0.5rem',
},
},
},
};

Filed Under: Angular, PrimeNG

Delete method in Angular

Generate OpenAPI

npm run openapi-gen:backend-api
npm run openapi-gen:chat-service-api

Template

<p-button
(onClick)="onDeleteConfirmOpen(result.id)"
icon="pi pi-trash"
/>

Component

protected readonly confirmationService = inject(ConfirmationService);

protected onDeleteConfirmOpen(id: string): void {
this.confirmationService.confirm({
header: this.localizationService.translate('SHARED.DELETE'),
message: this.localizationService.translate('SHARED.CONFIRM_DELETE_KNOWLEDGE_BASE'),
acceptLabel: this.localizationService.translate('SHARED.DELETE'),
rejectLabel: this.localizationService.translate('SHARED.CANCEL'),
accept: () => {
this.knowledgeBaseStore.deleteDocument(id);
}
});
}

Service – API Call

A KnowledgeBaseService felelős az API hívásokért. A delete metódus meghívja a generált documentDeleteDelete végpontot a megadott id-val.

public delete(id: string): Observable<void> {
return this._documentApiService.documentDeleteDelete({ id: id }).pipe(map(() => void 0));
}
public readAll(text: string): Observable<DocumentRead[]> {
return this._documentApiService
.documentReadAllGet({
text: text,
sort: '-searchScore',
isLatest: true,
isDeleted: false
})
.pipe(map((response) => response.results));
}

Store

A Store réteg köti össze a komponenst és a service-t. A deleteDocumentById metódus a KnowledgeBaseService.delete-et hívja, majd frissíti a lokális documents listát, kiszedi belőle a törölt elemet, és jelzi a sikeres törlést (deleteSuccess.set(true)).

private readonly _knowledgeBaseService = inject(KnowledgeBaseService);

public deleteDocument(id: string): void {
this.loading.set(true);
this._knowledgeBaseService
.delete(id)
.pipe(
tap(() => {
this.documents.update((docs) => docs.filter((d) => d.id !== id));
this.deleteSuccess.set(true);
}),
finalize(() => this.loading.set(false))
)
.subscribe();
}

A rétegekre bontás (Template → Component → Service → Store) azért jó, mert tisztán elválasztja a felelősségi köröket:

  • Template → csak a felhasználói felületért felel, gombok, inputok, eseménykötések. Nem tartalmaz logikát, csak azt, hogy mit lát a user.
  • Component → a UI logikát kezeli: mikor kell megerősítő dialógust nyitni, mikor kell a store-t hívni. Ez köti össze a template-et és az alkalmazás logikát.
  • Service → csak az API-hívásokkal foglalkozik. Itt van minden, ami a backenddel kommunikál. Így könnyen cserélhető, tesztelhető, és a komponensnek nem kell tudnia az API részleteit.
  • Store → az alkalmazás állapotát kezeli (lista, betöltés állapota, siker/hiba jelek). Ez a központi hely, ahol a komponensek és a service összekapcsolódnak. A store biztosítja, hogy több komponens is ugyanazt a friss adatot lássa.

Így a kód átláthatóbb, újrafelhasználhatóbb és könnyebben tesztelhető.

Filed Under: Angular, CRUD

Mailto with subject as Service

import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class MailtoService {
private readonly _email = 'hellodata@trendency.hu';
private readonly _subject = 'Ajánlatot kérek';

public openMailto(body?: string): void {
const mailtoUrl = `mailto:${this._email}?subject=${encodeURIComponent(this._subject)}${
body ? `&body=${encodeURIComponent(body)}` : ''
}`;

window.location.href = mailtoUrl;
}
}
private readonly _mailto = inject(MailtoService);

public mailtoWithSubject(): void {
this._mailto.openMailto();
}
<app-button [label]="'HEADER.LABEL' | transloco" (clickEvent)="mailtoWithSubject()" />

Filed Under: Angular

Mailto with subject

<app-button [label]="'HEADER.LABEL' | transloco" (clickEvent)="mailtoWithSubject()" />
public mailtoWithSubject(): void {
const email = 'hello@bello.hu';
const subject = 'Ajánlatot kérek';
// encodeURIComponent, hogy a szóköz és ékezetek is helyesen menjenek át
const mailtoUrl = `mailto:${email}?subject=${encodeURIComponent(subject)}`;

window.location.href = mailtoUrl;
}

Filed Under: Angular

Signal as Input

Az input() az Angular 16-ban bevezetett új API, ami:

  • @Input() helyett használható
  • egy writable signal-t ad vissza → tehát a komponensben úgy dolgozol vele, mintha sima signal lenne
  • a parentből továbbra is [pageTitle]-lal adod át az értéket
import { Component, ChangeDetectionStrategy, input } from '@angular/core';

@Component({
selector: 'app-content-layout',
templateUrl: './content-layout.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentLayoutComponent {
// Signal Input
public readonly pageTitle = input<string>('');
}
@if (pageTitle()) {
<h1 class="text-2xl font-semibold">
{{ pageTitle() }}
</h1>
}
<app-content-layout [pageTitle]="'TOPIC_GROUP.TOP10_TITLE' | transloco">

Így tehát:

  • Igen, a public readonly pageTitle = input<string>(''); egy signal input.
  • A pageTitle() hívás ugyanúgy működik, mint bármelyik signal esetén.
  • Az ESLint szabály (prefer-signals) pontosan ezért ajánlja.

Filed Under: Angular

Thousand Separator Directive

Only when typeof number

import {
Directive,
ElementRef,
Input,
Renderer2,
SimpleChanges,
OnInit,
OnChanges,
} from '@angular/core';
import { DecimalPipe } from '@angular/common';

@Directive({
selector: '[appThousandSeparator]',
providers: [DecimalPipe],
})
export class ThousandSeparatorDirective implements OnInit, OnChanges {
@Input('appThousandSeparator') num?: number | string;

constructor(
private el: ElementRef,
private renderer: Renderer2,
private decimalPipe: DecimalPipe
) {}

private updateNumber(): void {
if (typeof this.num === 'number') {
const formattedNumber = this.decimalPipe.transform(this.num, '1.0-0');
console.log('Formatted number: ', formattedNumber);
this.renderer.setProperty(
this.el.nativeElement,
'textContent',
formattedNumber
);
} else if (typeof this.num === 'string') {
this.renderer.setProperty(this.el.nativeElement, 'textContent', this.num);
} else {
this.renderer.setProperty(this.el.nativeElement, 'textContent', '');
}
}

ngOnInit(): void {
this.updateNumber();
}

ngOnChanges(changes: SimpleChanges): void {
if (changes['num']) {
this.updateNumber();
}
}
}

Filed Under: Angular

Auth Registration Example

import { Component, DestroyRef, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import {
MatCard,
MatCardActions,
MatCardContent,
MatCardHeader,
MatCardTitle,
} from '@angular/material/card';
import { MatFormField, MatLabel } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { Router, RouterLink } from '@angular/router';
import { AuthService } from '../auth.service';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({
selector: 'app-registration',
imports: [
MatCard,
MatCardHeader,
MatCardTitle,
MatCardContent,
MatButton,
MatFormField,
MatInput,
MatLabel,
MatCardActions,
MatSnackBarModule,
FormsModule,
RouterLink,
],
templateUrl: './registration.component.html',
styleUrl: './registration.component.scss',
standalone: true,
})
export class RegistrationComponent {
private readonly authService = inject(AuthService);
private readonly snackBar = inject(MatSnackBar);
private readonly router = inject(Router);
private readonly destroyRef = inject(DestroyRef);

email = signal<string>('');
password = signal<string>('');
passwordCheck = signal<string>('');

signUp() {
const emailValue = this.email();
const passwordValue = this.password();
const passwordCheckValue = this.passwordCheck();

if (!emailValue || !passwordValue || !passwordCheckValue) {
this.snackBar.open('All fields are required', 'Close', {
duration: 3000,
});
return;
}

if (passwordValue !== passwordCheckValue) {
this.snackBar.open('Passwords do not match', 'Close', { duration: 3000 });
return;
}

this.authService
.signUp(emailValue, passwordValue)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => {
this.snackBar.open('Registration successful!', 'Close', {
duration: 3000,
});
this.router.navigate(['/login']);
},
error: (error: { message: string }) => {
this.snackBar.open('Registration failed: ' + error.message, 'Close', {
duration: 3000,
});
},
});
}
}
<mat-card class="registration-card">
<mat-card-header class="registration-card-header">
<mat-card-title>Registration</mat-card-title>
</mat-card-header>
<mat-card-content class="registration-card-content">
<mat-form-field>
<mat-label>Email</mat-label>
<input type="text" matInput required [(ngModel)]="email" />
</mat-form-field>

<mat-form-field>
<mat-label>Password</mat-label>
<input type="password" matInput required [(ngModel)]="password" />
</mat-form-field>

<mat-form-field>
<mat-label>Password Check</mat-label>
<input type="password" matInput required [(ngModel)]="passwordCheck" />
</mat-form-field>
</mat-card-content>
<mat-card-actions class="registration-card-buttons">
<button mat-raised-button color="primary" (click)="signUp()">
Register
</button>
<button mat-raised-button color="secondary" [routerLink]="['/login']">
Back
</button>
</mat-card-actions>
</mat-card>

Filed Under: Angular

Auth Login Example

import { Component, DestroyRef, inject, signal } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { FormsModule } from '@angular/forms';
import { AuthService } from '../auth.service';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Router, RouterLink } from '@angular/router';

@Component({
selector: 'app-login',
imports: [
MatCardModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
FormsModule,
MatSnackBarModule,
RouterLink,
],
templateUrl: './login.component.html',
styleUrl: './login.component.scss',
standalone: true,
})
export class LoginComponent {
private readonly authService = inject(AuthService);
private readonly snackBar = inject(MatSnackBar);
private readonly destroyRef = inject(DestroyRef);
private readonly router = inject(Router);

email = signal('');
password = signal('');

signIn() {
if (!this.email() || !this.password()) {
this.snackBar.open('Please enter both email and password', 'Close', {
duration: 3000,
});
return;
}

this.authService
.signIn(this.email(), this.password())
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => {
this.router.navigate(['/cases']);
},
error: (error: { message: string }) => this.handleError(error),
});
}

private handleError(error: { message: string }) {
this.snackBar.open('Login failed: ' + error.message, 'Close', {
duration: 3000,
});
}
}
<mat-card class="login-card">
<mat-card-header class="login-card-header">
<mat-card-title class="login-card-title">Login</mat-card-title>
</mat-card-header>
<mat-card-content class="login-card-content">
<mat-form-field>
<mat-label>Email</mat-label>
<input type="text" matInput required [(ngModel)]="email" />
</mat-form-field>

<mat-form-field>
<mat-label>Password</mat-label>
<input type="password" matInput required [(ngModel)]="password" />
</mat-form-field>
</mat-card-content>
<mat-card-actions class="login-card-buttons">
<button mat-raised-button color="primary" (click)="signIn()">Login</button>
<button
mat-raised-button
color="secondary"
[routerLink]="['/registration']"
>
Registration
</button>
</mat-card-actions>
</mat-card>

Filed Under: Angular

Routing with Lazy Loading and Auth Guard

import { Routes } from '@angular/router';
import { authGuard } from './auth/auth.guard';

export const appRoutes: Routes = [
{
path: '',
children: [
{
path: '',
redirectTo: 'cases',
pathMatch: 'full',
},
{
path: 'cases',
canActivate: [authGuard], // Csak bejelentkezett felhasználóknak elérhető
loadComponent: () =>
import('./features/cases/cases.component').then(
(m) => m.CasesComponent
),
},
{
path: 'login',
canActivate: [authGuard], // Ha be vagy jelentkezve, átirányít a főoldalra
loadComponent: () =>
import('./auth/login/login.component').then((m) => m.LoginComponent),
},
{
path: 'registration',
canActivate: [authGuard], // Ha be vagy jelentkezve, átirányít a főoldalra
loadComponent: () =>
import('./auth/registration/registration.component').then(
(m) => m.RegistrationComponent
),
},
{
path: '404',
loadComponent: () =>
import('./core/not-found/not-found.component').then(
(m) => m.NotFoundComponent
),
},
{
path: '**',
redirectTo: '404',
},
],
},
];

Filed Under: Angular

Auth Service Example with FireBase

import { inject, Injectable, signal } from '@angular/core';
import {
Auth,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signOut,
UserCredential,
onAuthStateChanged,
User,
} from '@angular/fire/auth';
import { from, Observable } from 'rxjs';
import { Router } from '@angular/router';

@Injectable({
providedIn: 'root',
})
export class AuthService {
private readonly auth = inject(Auth);
private readonly router = inject(Router);

user = signal<User | null>(null);
isLoggedIn: boolean = false;

constructor() {
onAuthStateChanged(this.auth, (user) => {
this.user.set(user);
this.isLoggedIn = !!user;
if (user) {
console.log('Logged in as: ', user.email);
} else {
console.log('No user is logged in.');
this.router.navigate(['/login']);
}
});
}

getIsLoggedIn(): Promise<boolean> {
return new Promise((resolve) => {
onAuthStateChanged(this.auth, (user) => {
resolve(!!user);
});
});
}

signIn(email: string, password: string): Observable<UserCredential> {
return from(signInWithEmailAndPassword(this.auth, email, password));
}

signUp(email: string, password: string): Observable<UserCredential> {
return from(createUserWithEmailAndPassword(this.auth, email, password));
}

logOut() {
return signOut(this.auth);
}
}

Filed Under: Angular

  • « Go to Previous Page
  • Page 1
  • Page 2
  • Page 3
  • Page 4
  • Interim pages omitted …
  • Page 6
  • Go to Next Page »

Primary Sidebar

  • angular.io
© 2026 WP Flames - All Right Reserved