ng update @angular/core@17 @angular/cli@17
nvm use 20.11.0
ng update @angular/core@17 @angular/cli@17
nvm use 20.11.0

<div class="loader-wrapper">
<div class="loader">
<app-icon [icon]="'icon-spinner'" [size]="100"> </app-icon>
</div>
</div>
@use 'shared' as *;
:host {
display: block;
}
.loader-wrapper {
display: grid;
place-items: center;
height: 100vh;
}
@media (prefers-reduced-motion: no-preference) {
.loader {
animation: spinner infinite 2s linear;
transform-origin: center;
}
}
@keyframes spinner {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
(click)="setActive()"
@Output() activate = new EventEmitter<void>();
setActive() {
if (!this.isDisabled) {
this.activate.emit();
}
}
<your-component *ngFor="let item of menuItem; let i = index" [isActive]="i === activeItemIndex" (activate)="onActivate(i)" ></your-component>
activeItemIndex: number | null = null;
onActivate(index: number) {
this.activeItemIndex = index;
}
// Getter to return classes including backgroundColor
public get classes(): { [key: string]: boolean } {
const classList: { [key: string]: boolean } = {
loading: this.isLoading,
wide: this.isWide,
};
if (this.backgroundColor) {
classList[this.backgroundColor] = true;
}
return classList;
}
<div [ngClass]="classes"></div>
{{ number | number: '1.0-0' }}
420,000
420 000
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'thousandSeparator',
standalone: true,
})
export class ThousandSeparatorPipe implements PipeTransform {
transform(value: number): string {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
}
{{ number | thousandSeparator }}
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'thousandSeparator',
standalone: true,
})
export class ThousandSeparatorPipe implements PipeTransform {
public transform(value: number | string): string {
if (typeof value === 'string') {
const parsedValue = parseFloat(value.replace(/,/g, ''));
if (isNaN(parsedValue)) {
return value;
}
value = parsedValue;
}
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
}
A ternary operátor a következő formátumot követi: feltétel ? értékHaIgaz : értékHaHamis. Az [icon] attribútum értékét módosítjuk az isFavorite változó függvényében:
<div [ngClass]="isValid ? 'valid' : 'invalid'">
<app-icon [icon]="isFavorite ? 'icon-folder-star' : 'icon-folder'" [size]="84"></app-icon>
Ebben az esetben, ha isFavorite értéke true, akkor az [icon] attribútum értéke 'icon-folder-star' lesz, különben pedig 'icon-folder'. A [size] attribútum értéke mindkét esetben 84 marad, mivel nem változik az isFavorite változó függvényében.
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="icon-search" viewBox="0 0 14 14">
<path d="M13.3642 10.4131L11.9063 8.9199C11.5927 8.62179 11.1971 8.42427 10.7703 " />
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 14 14">
<path
stroke="currentColor"
d="M1.16675 6.99984H12.8334M12.8334 6.99984L7.00008 1.1665M12.8334 6.99984L7.00008 12.8332"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</symbol>
</defs>
</svg>
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
@Component({
selector: 'app-icon',
standalone: true,
imports: [CommonModule],
templateUrl: './app-icon.component.html',
styleUrl: './app-icon.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppIconComponent {
@Input() icon: string | undefined;
@Input() size: number | undefined;
}
<svg [attr.height]="size" [attr.width]="size"> <use [attr.href]="'images/icons/svg-icon-sprite.svg#' + icon"></use> </svg>
:host {
display: flex;
use {
display: block;
height: 100%;
width: 100%;
fill: currentColor;
}
}
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ButtonGroup, ButtonIcon, ButtonState, ButtonType } from '@app/lib/components/app-button/app-button.enum';
import { appIconComponent } from '../app-icon/app-icon.component';
import { appIconsComponent } from '../app-icons/app-icons.component';
@Component({
selector: 'app-app-button',
standalone: true,
imports: [CommonModule, appIconsComponent, appIconComponent],
templateUrl: './app-button.component.html',
styleUrl: './app-button.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class appButtonComponent {
@Input() label = 'Button';
@Input() buttonType: ButtonType = ButtonType.PRIMARY;
@Input() buttonGroup: ButtonGroup = ButtonGroup.DEFAULT;
@Input() buttonIcon: ButtonIcon = ButtonIcon.DEFAULT;
@Input() buttonState: ButtonState = ButtonState.DEFAULT;
@Output() buttonClick = new EventEmitter<Event>();
// Exponáljuk a ButtonIcon enumot, hogy használni tudjuk a template-ben
ButtonIcon = ButtonIcon;
ButtonType = ButtonType;
ButtonState = ButtonState;
public get classes(): string[] {
const typeClass = `app-btn-${this.buttonType}`;
const groupClass = `app-btn-${this.buttonGroup}`;
const iconClass = `app-btn-${this.buttonIcon}`;
const stateClass = `app-btn-${this.buttonState}`;
return ['app-btn', typeClass, groupClass, iconClass, stateClass];
}
}
<app-icon [icon]="'icon-arrow-right'" [size]="14"></app-icon>
export enum ButtonIcon {
DEFAULT = 'only text',
ICON_LEFT = 'icon left',
ICON_RIGHT = 'icon right',
ICON = 'icon',
}
<button [ngClass]="classes" (click)="buttonClick.emit($event)" type="button">
<ng-container *ngIf="buttonIcon === ButtonIcon.DEFAULT"> Default </ng-container>
<ng-container *ngIf="buttonIcon === ButtonIcon.ICON_LEFT"> Icon left </ng-container>
<ng-container *ngIf="buttonIcon === ButtonIcon.ICON_RIGHT"> Icon right </ng-container>
<ng-container *ngIf="buttonIcon === ButtonIcon.ICON"> Icon </ng-container>
</button>
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ButtonGroup, ButtonIcon } from '@app/lib/definitions/buttons.enum';
import { IconsComponent } from '../icons/icons.component';
@Component({
selector: 'app-button-primary',
standalone: true,
imports: [CommonModule, IconsComponent],
templateUrl: './button-primary.component.html',
styleUrl: './button-primary.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ButtonPrimaryComponent {
@Input() primary = false;
@Input() backgroundColor?: string;
@Input() label = 'Button';
@Input() iconColor = 'white';
@Input() buttonGroup: ButtonGroup = ButtonGroup.DEFAULT;
@Input() buttonIcon: ButtonIcon = ButtonIcon.DEFAULT;
@Output() buttonClick = new EventEmitter<Event>();
ButtonIcon = ButtonIcon;
public get classes(): string[] {
const groupClass = `app-button--${this.buttonGroup}`;
const iconClass = `app-button--${this.buttonIcon}`;
return ['app-button', groupClass, iconClass];
}
}
import { ButtonGroup, ButtonIcon } from '@app/lib/definitions/buttons.enum';
import type { Meta, StoryObj } from '@storybook/angular';
import { fn } from '@storybook/test';
import { ButtonPrimaryComponent } from './button-primary.component';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
const meta: Meta<ButtonPrimaryComponent> = {
title: 'app/Buttons/Button Primary',
component: ButtonPrimaryComponent,
tags: ['autodocs'],
argTypes: {
backgroundColor: {
control: 'color',
},
},
// Use `fn` to spy on the buttonClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { buttonClick: fn() },
};
export default meta;
type Story = StoryObj<ButtonPrimaryComponent>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Default: Story = {
args: {
primary: true,
label: 'Button',
buttonGroup: ButtonGroup.DEFAULT,
buttonIcon: ButtonIcon.DEFAULT,
},
};
export const Outlined: Story = {
args: {
label: 'Button',
buttonGroup: ButtonGroup.OUTLINED,
buttonIcon: ButtonIcon.DEFAULT,
},
};
export const Underlined: Story = {
args: {
label: 'Button',
buttonGroup: ButtonGroup.UNDERLINED,
},
};
export const Text: Story = {
args: {
label: 'Button',
buttonGroup: ButtonGroup.TEXT,
},
};
nvm inicializáló scriptje hozzá lett adva a shell konfigurációs fájlhoz. A Zsh esetében ez általában a ~/.zshrc fájl. Nyissa meg ezt a fájlt egy szövegszerkesztővel (pl. nano ~/.zshrc vagy vim ~/.zshrc) és ellenőrizze, hogy az alábbi sorok szerepelnek-e benne: export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"~/.zshrc fájlban, töltse be a konfigurációt újra a következő paranccsal: source ~/.zshrcnvm --version parancsot.nvm inicializáló parancsokat a ~/.zshrc fájlhoz, manuálisan is hozzáadhatja őket.Igen, az Angularban létrehozott reaktív form adatai alapján PDF-et lehet generálni. Ehhez általában egy harmadik féltől származó könyvtárat (pl. pdfmake) szokás használni.
Íme egy egyszerű lépésről lépésre terv a reaktív form adatai alapján PDF generálásához Angularban:
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
nev: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
// és így tovább...
});
}
pdfmake könyvtárat: Ezt a könyvtárat használhatja a PDF generálásához. Telepítheti npm vagy yarn segítségével: npm install pdfmake
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
pdfMake.vfs = pdfFonts.pdfMake.vfs;
generatePDF() {
if (this.form.valid) {
const docDefinition = {
content: [
{ text: 'Adatok', style: 'header' },
{ text: `Név: ${this.form.get('nev').value}` },
{ text: `Email: ${this.form.get('email').value}` },
// és így tovább...
]
};
pdfMake.createPdf(docDefinition).download('adatok.pdf');
} else {
alert('Kérjük, töltse ki az összes szükséges mezőt.');
}
}
<form [formGroup]="form">
<!-- űrlap elemek ... -->
<button (click)="generatePDF()">PDF generálása</button>
</form>
Ez egy nagyon egyszerű példa, de természetesen további beállításokkal, stílusokkal és egyéb opciókkal is bővítheti a generált PDF tartalmát és kinézetét.
A pdfmake könyvtár számos beállítást és stílusopciót kínál a PDF tartalmának és kinézetének testreszabásához. Íme néhány példa, amelyek segíthetnek a PDF tartalmának stílusos formázásában:
bold: Igaz/hamis értékkel állítható.italics: Igaz/hamis értékkel állítható.color: Hexadecimális színkódokat használ.fontSize: A szöveg mérete pontokban.font: Betűtípus neve.table: Táblázat adatok és beállítások.layout: A táblázat elrendezése.ul: Nem rendezett lista.ol: Rendezett lista.margin: [felső, jobb, alsó, bal] margó beállítások.alignment: Szöveg igazítása (left, right, center stb.)Íme egy példa a fenti stílusok alkalmazására:
const docDefinition = {
content: [
{ text: 'Cím', style: 'header' },
{
text: `Név: ${this.form.get('nev').value}`,
fontSize: 14,
bold: true,
margin: [0, 0, 0, 10]
},
{
text: `Email: ${this.form.get('email').value}`,
italics: true,
color: '#666666'
},
{
table: {
body: [
['Fejléc1', 'Fejléc2'],
['Adat1', 'Adat2'],
['Adat3', 'Adat4']
]
},
layout: 'lightHorizontalLines'
},
{
ol: [
'Első elem',
'Második elem',
'Harmadik elem'
],
margin: [0, 20]
}
],
styles: {
header: {
fontSize: 22,
bold: true,
margin: [0, 0, 0, 10]
}
}
};
pdfMake.createPdf(docDefinition).download('adatok.pdf');
A fenti kód példázza, hogyan lehet az alapvető stílusokat és egyéb formázási lehetőségeket alkalmazni a pdfmake könyvtár segítségével. A hivatalos dokumentációban sokkal több stílus és opció található, így érdemes ott további részletekért és lehetőségekért keresgélni.
A PDF-et egy e-mailhez csatolva nem lehet egyszerűen és közvetlenül az ügyfél oldaláról elküldeni a böngésző korlátozásai miatt. Azonban a következő általános lépéseket követve megoldható:
pdfmake-kel, elmentheti azt a memóriában, vagy közvetlenül feltöltheti egy szerverre, ahelyett, hogy letöltené azt.Példaként egy egyszerű Express.js backend használatával és a nodemailer könyvtárral való e-mail küldésre:
const express = require('express');
const nodemailer = require('nodemailer');
const app = express();
app.post('/send-email', async (req, res) => {
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'your-email@gmail.com',
pass: 'your-password'
}
});
let info = await transporter.sendMail({
from: 'your-email@gmail.com',
to: 'recipient-email@example.com',
subject: 'Your PDF',
text: 'Here is your PDF!',
attachments: [{
filename: 'document.pdf',
path: 'path/to/your/pdf' // This should be the path where you saved your PDF
}]
});
res.send('Email sent!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Figyelem: A fenti példában a Gmailt használjuk az e-mailek küldésére, de ez nem biztonságos, és nem ajánlott produkciós környezetben. Ehelyett használjon hivatalos e-mail küldő szolgáltatást, mint amilyen a SendGrid vagy a Mailgun.
Emellett győződjön meg róla, hogy az érzékeny adatokat, mint például a jelszavakat vagy az API kulcsokat, nem tárolja közvetlenül a kódban, hanem környezeti változókban vagy titkosított konfigurációs fájlokban.