{{ uploadTime | date: 'yyyy.MM.dd hh:mm' }}
Other
Button
import { ButtonGroup, ButtonIcon, ButtonType } from '@app/shared/components/button/button.enum';
public ButtonGroup = ButtonGroup;
public ButtonIcon = ButtonIcon;
public ButtonType = ButtonType;
<app-button
[label]="'Rendezés'"
[buttonType]="ButtonType.PRIMARY"
[buttonGroup]="ButtonGroup.DEFAULT"
[buttonIcon]="ButtonIcon.DEFAULT"
[isWide]="true"
[isDisabled]="false"
[isDarkmode]="false"
>
</app-button>
Classes getter
public get classes(): string[] {
const deviceType = this.isMobile ? 'mobile' : 'desktop';
return [deviceType];
}
public get classes(): string[] {
const type = `${this.type}`;
return [type];
}
public get classes(): string[] {
const type = `${this.type}`;
const deviceType = this.isMobile ? 'mobile' : 'desktop';
return [type, deviceType];
}
getClasses(): string[] {
const classes = [];
switch (this.data.status) {
case 'unread':
classes.push('unread');
break;
case 'read':
classes.push('read');
break;
case 'archived':
classes.push('archived');
break;
}
return classes;
}
Password Validation
<form class="password-validator" [formGroup]="passwordForm">
<div class="password-validator-fields">
<app-field
class="mb-24"
[ngClass]="{ 'state-error': !isFormValid && passwordForm.get('password')?.touched }"
[fieldType]="FieldType.PASSWORD"
[label]="'SHARED.PASSWORD' | transloco"
[isIcon]="true"
[isIconRight]="true"
[fieldState]="passwordFieldState"
formControlName="password"
></app-field>
<app-field
class="mb-24"
[ngClass]="{ 'state-error': !isFormValid && passwordAgainTouched }"
[fieldType]="FieldType.PASSWORD"
[label]="'SHARED.PASSWORD_AGAIN' | transloco"
[isIcon]="true"
[isIconRight]="true"
[fieldState]="passwordAgainFieldState"
formControlName="passwordAgain"
></app-field>
</div>
<div class="password-validator-message">
<app-password-check
[validationText]="'AUTH.MIN_CHARACTER' | transloco"
[isValid]="isMinCharacter"
></app-password-check>
<app-password-check
[validationText]="'AUTH.MIN_LOWERCASE' | transloco"
[isValid]="hasMinLowercase"
></app-password-check>
<app-password-check
[validationText]="'AUTH.MIN_UPPERCASE' | transloco"
[isValid]="hasMinUppercase"
></app-password-check>
<app-password-check
[validationText]="'AUTH.MIN_NUMBER' | transloco"
[isValid]="hasMinNumber"
></app-password-check>
<app-password-check
*ngIf="passwordsMismatch && passwordAgainTouched"
[validationText]="'AUTH.PASSWORD_MISMATCH' | transloco"
[isValid]="!passwordsMismatch"
></app-password-check>
</div>
</form>
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
ValidatorFn,
Validators,
} from '@angular/forms';
import { FieldComponent } from '@app/lib/components/field/field.component';
import { FieldState, FieldType } from '@app/lib/components/field/field.enum';
import { PasswordCheckComponent } from '@app/lib/components/password-check/password-check.component';
import { TranslocoPipe } from '@jsverse/transloco';
@Component({
selector: 'app-password-validator',
standalone: true,
imports: [CommonModule, FieldComponent, TranslocoPipe, PasswordCheckComponent, ReactiveFormsModule],
templateUrl: './password-validator.component.html',
styleUrls: ['./password-validator.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PasswordValidatorComponent implements OnInit {
passwordForm: FormGroup;
@Input() validationText: string;
isFormValid: boolean = false;
FieldType = FieldType;
FieldState = FieldState;
constructor(private readonly fb: FormBuilder) {
this.passwordForm = this.fb.group(
{
password: ['', [Validators.required, Validators.minLength(8)]],
passwordAgain: ['', Validators.required],
},
{ validators: this.passwordsMatch() },
);
}
ngOnInit(): void {
this.passwordForm.valueChanges.subscribe(() => {
this.checkFormValidity();
});
}
private passwordsMatch(): ValidatorFn {
return (formGroup: AbstractControl): ValidationErrors | null => {
const passwordControl = formGroup.get('password');
const passwordAgainControl = formGroup.get('passwordAgain');
if (!passwordControl || !passwordAgainControl) {
return null;
}
const password = passwordControl.value;
const passwordAgain = passwordAgainControl.value;
return password === passwordAgain ? null : { passwordsMismatch: true };
};
}
get isMinCharacter(): boolean {
return (this.passwordForm.get('password')?.valid && this.passwordForm.get('password')?.value?.length >= 8) || false;
}
get hasMinLowercase(): boolean {
return /[a-z]/.test(this.passwordForm.get('password')?.value || '');
}
get hasMinUppercase(): boolean {
return /[A-Z]/.test(this.passwordForm.get('password')?.value || '');
}
get hasMinNumber(): boolean {
return /[0-9]/.test(this.passwordForm.get('password')?.value || '');
}
get passwordsMismatch(): boolean {
return this.passwordForm.hasError('passwordsMismatch');
}
get passwordAgainTouched(): boolean {
return this.passwordForm.get('passwordAgain')?.touched || false;
}
get passwordFieldState(): FieldState {
return !this.isFormValid && this.passwordForm.get('password')?.touched
? this.FieldState.ERROR
: this.FieldState.DEFAULT;
}
get passwordAgainFieldState(): FieldState {
return !this.isFormValid && this.passwordAgainTouched ? this.FieldState.ERROR : this.FieldState.DEFAULT;
}
private checkFormValidity(): void {
this.isFormValid =
this.isMinCharacter &&
this.hasMinLowercase &&
this.hasMinUppercase &&
this.hasMinNumber &&
!this.passwordsMismatch &&
this.passwordForm.valid;
console.log(this.isFormValid ? 'Valid' : 'Not valid');
}
}
Mock.ts
mock.service.ts
export class MockService {
getCompanyResultData(): readonly CompanyResult[] {
return MockCompanyResult;
}
}
component.ts
private readonly _mockService = inject(MockService);
ngOnInit(): void {
this.data.companyResult = this._mockService.getCompanyResultData();
}
model.ts
export interface YourComponent {
companyResult?: readonly CompanyResult[];
}
mock.ts
export const MockCompanyResult: readonly CompanyResult[] = [
{
companyTitle: 'Hello Online Kft.',
taxNumberText: 'Adószám',
taxNumber: '123456789',
},
{
companyTitle: 'Hello Élelmiszeripari Kft.',
taxNumberText: 'Adószám',
taxNumber: '123456789',
},
{
companyTitle: 'Hello Pékség',
taxNumberText: 'Adószám',
taxNumber: '123456789',
},
{
companyTitle: 'Hello Software Kft.',
taxNumberText: 'Adószám',
taxNumber: '123456789',
},
];
Transloco
"CONFIRM_DELETE": "Biztosan törölni szeretnéd a {{title}} tudásanyagot?"
<p-button (onClick)="onDeleteConfirmOpen(result.id, result.title)" />
protected onDeleteConfirmOpen(id: string, title: string): void {
this.confirmationService.confirm({
message: this.localizationService.translate('SHARED.CONFIRM_DELETE', { title }),
});
}
label: this._localizationService.translate('SHARED.DELETE'),
[label]="`Médiumok felvétele a ${mediumType() === MediumType.PORTFOLIO ? 'portfólióba' : 'kedvencek közé'}`"
[label]="
'MEDIUM.ADD_TO'
| transloco
: {
type:
mediumType() === MediumType.PORTFOLIO
? ('MEDIUM.PORTFOLIO' | transloco)
: ('MEDIUM.FAVORITES' | transloco)
}
"
"MEDIUM": {
"ADD_TO": "Médiumok felvétele a {{ type }}",
"PORTFOLIO": "portfólióba",
"FAVORITES": "kedvencek közé"
}
translocoService
import { TranslocoService } from '@jsverse/transloco';
@Injectable({ providedIn: 'root' })
export class ProfileOperationsService extends OperationsServicesBase {
private readonly _transloco = inject(TranslocoService);
public onPasswordChangeDialogOpen(): void {
this._dialogRef = this._dialogService.open(ChangePwComponent, {
header: this._transloco.translate('AUTH.CHANGE_PW_TITLE'),
message: `${this._transloco.translate('MEDIUM.REMOVE_ARE_YOU_SURE')}<br /><br /><strong>${medium.name}</strong>`,
data: {},
closable: true,
width: '720px'
});
}
}
transloco-loader.ts
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Translation, TranslocoLoader } from '@jsverse/transloco';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoader implements TranslocoLoader {
private readonly _http = inject(HttpClient);
public getTranslation(lang: string): Observable<Translation> {
return this._http.get<Translation>(`/assets/locales/${lang}.json`);
}
}
Localized URLs
import { TranslocoPipe, TranslocoService } from '@jsverse/transloco';
translocoService = inject(TranslocoService);
<a [routerLink]="['/',translocoService.getActiveLang(),'cookie-policy']">Cookie Policy</a>
<a [routerLink]="['/',translocoService.getActiveLang(),'terms-and-conditions']">Terms and conditions</a>
<a [routerLink]="['/',translocoService.getActiveLang(),'privacy-policy']">Privacy Policy</a>
Transloco with mock data
<ng-container *ngFor="let item of data; let i = index">
<app-accordion
[title]="item.title | transloco"
[desc]="item.desc | transloco"
/>
</ng-container>
import { Injectable } from '@angular/core';
import { Card } from '@app/shared';
@Injectable({
providedIn: 'root'
})
export class FaqService {
public getData(): readonly Card[] {
return [
{ title: 'FAQ_1.Q', desc: 'FAQ_1.A' },
{ title: 'FAQ_2.Q', desc: 'FAQ_2.A' },
{ title: 'FAQ_3.Q', desc: 'FAQ_3.A' },
];
}
}
export class FaqComponent implements OnInit {
private readonly _faqService = inject(FaqService);
public data: readonly Card[];
public ngOnInit(): void {
this.data = this._faqService.getData();
}
}
Story
decorators: [
applicationConfig({
providers: [importProvidersFrom(getTranslocoModule())],
}),
],
{{ 'STORAGE.RENAME' | transloco }}
[modalTitle]="'STORAGE.RENAME' | transloco"
app.config.ts
import { registerLocaleData } from '@angular/common';
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import hu from '@angular/common/locales/hu';
import { APP_INITIALIZER, ApplicationConfig, isDevMode } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideTransloco } from '@jsverse/transloco';
import { TranslocoHttpLoader } from 'src/transloco-loader';
import { appRoutes } from './app.routes';
import { AppInitService } from './core';
import { apiInterceptor } from './core/interceptors/api.interceptor';
registerLocaleData(hu);
export function appInitProviderFactory(provider: AppInitService) {
return (): Promise<void> => provider.initialize();
}
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(appRoutes),
provideClientHydration(),
provideHttpClient(withInterceptors([apiInterceptor]), withFetch()),
{
provide: APP_INITIALIZER,
useFactory: appInitProviderFactory,
deps: [AppInitService],
multi: true
},
provideTransloco({
config: {
availableLangs: ['hu'],
defaultLang: 'hu',
prodMode: !isDevMode()
},
loader: TranslocoHttpLoader
})
]
};
assets/locales/hu.json
{
"MENU": {
"HOME": "Kezdőlap"
}
}
Refactor @Input
import { PortalCard } from './portal-card.interface';
export interface Portal {
allCards: number;
selectedCards: number;
title: string;
subTitle: string;
textAbout: string;
expectedAppearanceText: string;
expectedAppearanceNum: number;
icon?: string;
isOpen: boolean;
isAllChecked: boolean;
portalCards: PortalCard[];
}
@Input() data: Portal
<div>{{ data.selectedCards }}/{{ data.allCards }}</div>
Classes getter method
public get classes(): string[] {
const type = `${this.type}`;
const deviceType = this.isMobile ? 'mobile' : 'desktop';
return [type, deviceType];
}
<div [ngClass]="classes"></div>
Mock Cover
export interface Cover {
caption: Caption;
image: Image;
}
export interface Caption {
title: string;
subtitle: string;
text: string;
btnLabel: string;
btnLink: string;
}
export interface Image {
src: string;
alt: string;
}
import { Cover } from './cover.model';
export const MockCover: Cover = {
caption: {
title: '',
text: '',
btnLabel: '',
btnLink: ''
},
image: {
src: '',
alt: '',
},
};
import { Injectable } from '@angular/core';
import { MockCover } from '../components/cover/cover.mock';
@Injectable({
providedIn: 'root',
})
export class MockService {
constructor() {}
getMockData() {
return MockCover;
}
}
export class CoverComponent {
mockData?: Cover;
constructor(private mockService: MockService) {
this.mockData = this.mockService.getMockData();
}
}
<div class="cover">
<div class="cover-caption">
<h1 class="cover-title">{{ mockData?.caption?.title }}</h1>
<p class="cover-description">
{{ mockData?.caption?.subtitle }}
</p>
<p class="cover-text">
{{ mockData?.caption?.text }}
</p>
<app-button
[btnLabel]="mockData?.caption?.btnLabel"
[btnLink]="mockData?.caption?.btnLink"
></app-button>
</div>
<figure class="cover-figure">
<img
class="cover-image"
[src]="mockData?.image?.src"
[alt]="mockData?.image?.alt"
/>
</figure>
</div>
Nem indul az Angular 17
Az Angular 14 verziójától kezdve bevezetésre került a standalone komponensek fogalma, ami lehetővé teszi, hogy a komponensek és direktívák modulfüggetlenek legyenek. Ez a funkció azonban eltérő bootstrapelést igényel.
Az alábbiakban bemutatom, hogyan tudod a standalone komponenst bootstrapelni az @NgModule használata helyett a bootstrapApplication segítségével.
1. AppModule helyett main.ts használata
main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent)
.catch(err => console.error(err));
2. AppComponent marad ugyanaz
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [],
})
export class AppComponent {
title = 'weitz-landing-1';
}
3. Távolítsd el az AppModule-t
app.module.ts
Ez a fájl már nem szükséges, így eltávolíthatod.
4. HTML és SCSS
app.component.html
HELLO
app.component.scss
Hagyd meg a jelenlegi tartalommal.
Összefoglalás
Ezekkel a módosításokkal a standalone komponenst közvetlenül bootstrappelheted az Angular alkalmazásodban. Nincs szükség @NgModule definícióra, ehelyett a bootstrapApplication függvényt használjuk, ami az új ajánlott módszer standalone komponensek esetén.