Biblioteki frontendowe¶
Wprowadzenie¶
Projekt masaku-portal wykorzystuje Nx monorepo z 32+ bibliotekami, każda odpowiedzialna za konkretną domenę lub funkcjonalność.
Organizacja bibliotek¶
Kategorie bibliotek¶
Biblioteki są podzielone na kilka kategorii:
1. Core Libraries (Wspólne/Fundamentalne)¶
| Biblioteka | Scope | Opis |
|---|---|---|
common |
@msk/common |
Wspólne utilities, services, constants, abstract base classes |
types |
@msk/types |
TypeScript interfaces i types |
helpers |
@msk/helpers |
Funkcje pomocnicze |
utils |
@msk/utils |
Generalne utilities |
validators |
@msk/validators |
Logika walidacji formularzy |
environment |
@msk/environment |
Konfiguracja środowiska |
storage |
@msk/storage |
Browser storage utilities |
destroy |
@msk/destroy |
Component cleanup utilities |
2. Feature Libraries (Domeny biznesowe)¶
| Biblioteka | Scope | Opis |
|---|---|---|
budgets |
@msk/budgets |
Zarządzanie budżetami (osobiste i współdzielone) |
clients |
@msk/clients |
Dane klientów i formularze |
employments |
@msk/employments |
Zarządzanie zatrudnieniem |
expenses |
@msk/expenses |
Śledzenie wydatków (bezpośrednie, ogólne, podróże) |
invoices |
@msk/invoices |
Przetwarzanie faktur |
receipts |
@msk/receipts |
Obsługa paragonów |
orders |
@msk/orders |
Zarządzanie zamówieniami |
members |
@msk/members |
Zarządzanie członkami |
userships |
@msk/userships |
Relacje użytkowników |
community |
@msk/community |
Funkcje społeczności |
governance |
@msk/governance |
Reguły i kontrola governance |
months |
@msk/months |
Obsługa danych miesięcznych |
3. Portal-specific Libraries (Portale)¶
| Biblioteka | Scope | Opis |
|---|---|---|
cp |
@msk/cp |
Client Portal |
mp |
@msk/mp |
Member Portal |
op |
@msk/op |
Office Portal |
ucp |
@msk/ucp |
Unified Client Portal |
4. UI/UX Libraries¶
| Biblioteka | Scope | Opis |
|---|---|---|
ui |
@msk/ui |
Reużywalne komponenty UI i formularze |
i18n |
@msk/i18n |
Internationalization |
micro-loader |
@msk/micro-loader |
Micro-frontend loader |
entry |
@msk/entry |
Application entry points |
list |
@msk/list |
List components |
5. State Management Libraries¶
| Biblioteka | Scope | Opis |
|---|---|---|
store |
@msk/store |
NGXS state (settings, user) |
options |
@msk/options |
Application options state |
settings |
@msk/settings |
Application settings |
Szczegółowy opis kluczowych bibliotek¶
@msk/common¶
Lokalizacja: projects/common/
Fundamentalna biblioteka zawierająca wspólne utilities i abstrakcyjne klasy bazowe.
Struktura:
projects/common/
├── src/
│ ├── lib/
│ │ ├── abstract/ # Abstract base classes
│ │ │ ├── abstract-api.service.ts
│ │ │ ├── abstract-form.service.ts
│ │ │ ├── abstract-mapper.service.ts
│ │ │ ├── abstract-preview.service.ts
│ │ │ └── abstract-actions.service.ts
│ │ │
│ │ ├── services/ # Base services
│ │ │ ├── base-api.service.ts
│ │ │ ├── http.service.ts
│ │ │ └── notification.service.ts
│ │ │
│ │ ├── constants/ # Stałe
│ │ │ └── api-endpoints.ts
│ │ │
│ │ ├── utils/ # Utilities
│ │ │ ├── date.utils.ts
│ │ │ ├── string.utils.ts
│ │ │ └── array.utils.ts
│ │ │
│ │ └── index.ts
│ └── index.ts
Kluczowe eksporty:
// Abstract classes
export { AbstractApiService } from './lib/abstract/abstract-api.service';
export { AbstractFormService } from './lib/abstract/abstract-form.service';
export { AbstractMapperService } from './lib/abstract/abstract-mapper.service';
// Base services
export { BaseApiService } from './lib/services/base-api.service';
export { HttpService } from './lib/services/http.service';
// Utils
export * from './lib/utils/date.utils';
export * from './lib/utils/string.utils';
Przykład użycia:
import { AbstractApiService } from '@msk/common';
export class BudgetApiService extends AbstractApiService<Budget> {
protected apiUrl = '/api/budgets';
// Automatycznie dziedziczą getAll(), getById(), create(), update(), delete()
}
@msk/types¶
Lokalizacja: projects/types/
Centralna biblioteka dla wszystkich TypeScript interfaces i types.
Struktura:
projects/types/
├── src/
│ ├── lib/
│ │ ├── models/
│ │ │ ├── budget.model.ts
│ │ │ ├── client.model.ts
│ │ │ ├── expense.model.ts
│ │ │ ├── invoice.model.ts
│ │ │ └── ...
│ │ │
│ │ ├── dtos/
│ │ │ ├── budget.dto.ts
│ │ │ ├── client.dto.ts
│ │ │ └── ...
│ │ │
│ │ ├── enums/
│ │ │ ├── expense-type.enum.ts
│ │ │ ├── invoice-status.enum.ts
│ │ │ └── ...
│ │ │
│ │ └── index.ts
│ └── index.ts
Przykład modeli:
// budget.model.ts
export interface Budget {
id: string;
name: string;
amount: number;
startDate: Date;
endDate?: Date;
userId: string;
shared: boolean;
currency: Currency;
status: BudgetStatus;
}
export enum BudgetStatus {
Active = 'active',
Inactive = 'inactive',
Completed = 'completed'
}
export enum Currency {
EUR = 'EUR',
USD = 'USD'
}
@msk/budgets¶
Lokalizacja: projects/budgets/
Moduł zarządzania budżetami (osobiste i współdzielone).
Struktura:
projects/budgets/
├── src/
│ ├── lib/
│ │ ├── components/
│ │ │ ├── budget-list/
│ │ │ ├── budget-form/
│ │ │ ├── budget-detail/
│ │ │ └── shared-budget/
│ │ │
│ │ ├── services/
│ │ │ ├── budget-api.service.ts
│ │ │ ├── budget.service.ts
│ │ │ └── budget-form.service.ts
│ │ │
│ │ ├── state/
│ │ │ ├── budget.state.ts
│ │ │ ├── budget.actions.ts
│ │ │ └── budget.selectors.ts
│ │ │
│ │ ├── models/ # Specificzne dla modułu
│ │ │ └── budget-summary.model.ts
│ │ │
│ │ ├── constants/
│ │ │ └── budget-constants.ts
│ │ │
│ │ ├── budgets.module.ts
│ │ ├── budgets-routing.module.ts
│ │ └── index.ts
│ └── index.ts
State management:
// budget.state.ts
export interface BudgetStateModel {
budgets: Budget[];
selectedBudget: Budget | null;
loading: boolean;
error: string | null;
}
@State<BudgetStateModel>({
name: 'budgets',
defaults: {
budgets: [],
selectedBudget: null,
loading: false,
error: null
}
})
@Injectable()
export class BudgetState {
// Selectors, actions, etc.
}
@msk/expenses¶
Lokalizacja: projects/expenses/
Moduł śledzenia wydatków (bezpośrednie, ogólne, podróże).
Kluczowe funkcjonalności: - Rejestrowanie wydatków w trzech kategoriach - Przypisywanie do budżetów - Przechowywanie paragonów/faktur - Rozliczanie wydatków służbowych
State model:
export interface ExpenseStateModel {
expenses: Expense[];
expenseTypes: ExpenseType[];
selectedExpense: Expense | null;
filters: ExpenseFilters;
loading: boolean;
}
export enum ExpenseType {
Direct = 'direct', // Wydatki bezpośrednie
General = 'general', // Wydatki ogólne
Travel = 'travel' // Wydatki podróży służbowych
}
@msk/invoices¶
Lokalizacja: projects/invoices/
Moduł przetwarzania faktur.
Kluczowe funkcjonalności: - Wystawianie faktur VAT - Faktury pro forma - Faktury korygujące - Śledzenie płatności - Generowanie PDF
Struktura:
projects/invoices/
├── components/
│ ├── invoice-list/
│ ├── invoice-form/
│ ├── invoice-preview/
│ └── invoice-pdf/
├── services/
│ ├── invoice-api.service.ts
│ ├── invoice-pdf.service.ts
│ └── invoice-calculation.service.ts
├── state/
│ ├── invoice.state.ts
│ └── invoice.actions.ts
└── models/
├── invoice-item.model.ts
└── invoice-payment.model.ts
@msk/receipts¶
Lokalizacja: projects/receipts/
Moduł obsługi paragonów z OCR.
Kluczowe funkcjonalności: - Upload paragonów (zdjęcia) - OCR (rozpoznawanie tekstu) - Archiwizacja dokumentów - Integracja z Azure Blob Storage
Services:
@Injectable()
export class ReceiptOcrService {
async extractText(image: File): Promise<ReceiptData> {
// Wywołanie Azure Cognitive Services OCR
}
}
@Injectable()
export class ReceiptStorageService {
async upload(file: File): Promise<string> {
// Upload do Azure Blob Storage
}
}
@msk/store (NGXS State)¶
Lokalizacja: projects/store/
Centralna biblioteka zarządzania stanem globalnym aplikacji.
Struktura:
projects/store/
├── src/
│ ├── lib/
│ │ ├── user/
│ │ │ ├── user.state.ts
│ │ │ ├── user.actions.ts
│ │ │ └── user.selectors.ts
│ │ │
│ │ ├── settings/
│ │ │ ├── settings.state.ts
│ │ │ └── settings.actions.ts
│ │ │
│ │ ├── auth/
│ │ │ ├── auth.state.ts
│ │ │ └── auth.actions.ts
│ │ │
│ │ └── index.ts
│ └── index.ts
User State:
export interface UserStateModel {
currentUser: User | null;
isAuthenticated: boolean;
permissions: string[];
preferences: UserPreferences;
}
@State<UserStateModel>({
name: 'user',
defaults: {
currentUser: null,
isAuthenticated: false,
permissions: [],
preferences: {}
}
})
@Injectable()
export class UserState {
@Selector()
static getCurrentUser(state: UserStateModel): User | null {
return state.currentUser;
}
@Selector()
static isAuthenticated(state: UserStateModel): boolean {
return state.isAuthenticated;
}
@Action(LoginUser)
loginUser(ctx: StateContext<UserStateModel>, action: LoginUser) {
// Login logic
}
}
@msk/ui¶
Lokalizacja: projects/ui/
Reużywalne komponenty UI.
Struktura:
projects/ui/
├── components/
│ ├── buttons/
│ │ ├── primary-button/
│ │ ├── secondary-button/
│ │ └── icon-button/
│ │
│ ├── forms/
│ │ ├── input/
│ │ ├── select/
│ │ ├── datepicker/
│ │ ├── file-upload/
│ │ └── textarea/
│ │
│ ├── layouts/
│ │ ├── card/
│ │ ├── panel/
│ │ └── container/
│ │
│ ├── modals/
│ │ ├── dialog/
│ │ └── confirmation-dialog/
│ │
│ └── tables/
│ ├── data-table/
│ └── paginated-table/
Przykład komponentu:
@Component({
selector: 'msk-input',
template: `
<mat-form-field [appearance]="appearance">
<mat-label>{{ label }}</mat-label>
<input
matInput
[formControl]="control"
[placeholder]="placeholder"
[type]="type"
/>
<mat-error *ngIf="control.hasError('required')">
{{ 'validation.required' | transloco }}
</mat-error>
</mat-form-field>
`,
styleUrls: ['./input.component.scss']
})
export class InputComponent {
@Input() label!: string;
@Input() placeholder = '';
@Input() type = 'text';
@Input() appearance: MatFormFieldAppearance = 'outline';
@Input() control!: FormControl;
}
@msk/i18n¶
Lokalizacja: projects/i18n/
Internationalization z @ngneat/transloco.
Wspierane języki: - Niemiecki (de) - domyślny - Angielski (en) - Polski (pl) - opcjonalnie
Struktura tłumaczeń:
Transloco config:
export const translocoConfig: TranslocoConfig = {
availableLangs: ['de', 'en', 'pl'],
defaultLang: 'de',
fallbackLang: 'en',
reRenderOnLangChange: true,
prodMode: environment.production
};
@msk/validators¶
Lokalizacja: projects/validators/
Custom validators dla Angular Reactive Forms.
Validators:
// email-validator.ts
export function emailValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (!value) return null;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value) ? null : { email: true };
};
}
// date-range-validator.ts
export function dateRangeValidator(
startDateField: string,
endDateField: string
): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const startDate = group.get(startDateField)?.value;
const endDate = group.get(endDateField)?.value;
if (startDate && endDate && startDate > endDate) {
return { dateRange: true };
}
return null;
};
}
// vat-number-validator.ts
export function vatNumberValidator(country: 'DE' | 'AT'): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (!value) return null;
const patterns = {
DE: /^DE\d{9}$/,
AT: /^ATU\d{8}$/
};
return patterns[country].test(value) ? null : { vatNumber: true };
};
}
@msk/destroy¶
Lokalizacja: projects/destroy/
Utilities do automatycznego oczyszczania subskrypcji.
Użycie:
import { UntilDestroy, untilDestroyed } from '@msk/destroy';
@UntilDestroy()
@Component({
selector: 'msk-my-component',
template: `...`
})
export class MyComponent implements OnInit {
ngOnInit() {
this.myService.getData()
.pipe(untilDestroyed(this))
.subscribe(data => {
// Automatycznie odsubskrybuje przy zniszczeniu komponentu
});
}
}
Dependency Graph¶
┌─────────────────┐
│ Main App │
└────────┬────────┘
│
├──► Feature Libraries (budgets, clients, expenses, etc.)
│ └──► Common, Types, UI
│
├──► Store (NGXS global state)
│
├──► UI (shared components)
│ └──► Common, Types
│
└──► i18n, Validators, Helpers, Utils
└──► Common, Types
Nx Graph¶
Aby zobaczyć interaktywny graf zależności:
Otworzy się przeglądarka z wizualizacją wszystkich zależności między bibliotekami.
Best Practices¶
1. Public API¶
Każda biblioteka powinna eksportować tylko to, co jest potrzebne innym bibliotekom:
// projects/budgets/src/index.ts
// Public API
export * from './lib/budgets.module';
export * from './lib/components';
export * from './lib/services/budget.service';
export * from './lib/state/budget.state';
// NIE eksportuj wewnętrznych detali
// export * from './lib/services/internal.service'; ❌
2. Circular Dependencies¶
Unikaj cyklicznych zależności. Jeśli biblioteka A zależy od B, to B nie może zależeć od A.
Nx automatycznie wykrywa circular dependencies podczas buildu.
3. Naming Convention¶
// Services
BudgetService, BudgetApiService, BudgetFormService
// Components
BudgetListComponent, BudgetFormComponent
// State
BudgetState, BudgetActions
// Models
Budget, BudgetDto, BudgetSummary
4. Library Size¶
Staraj się, żeby biblioteki nie były za duże. Jeśli biblioteka ma >100 plików, rozważ podział na mniejsze biblioteki.
5. Testing¶
Każda biblioteka powinna mieć swoje testy:
# Test konkretnej biblioteki
nx test budgets
# Test wszystkich bibliotek
nx run-many --target=test --all
Generowanie nowej biblioteki¶
# Generowanie nowej biblioteki
nx generate @nrwl/angular:library my-library --directory=projects
# Z routingiem
nx generate @nrwl/angular:library my-library --directory=projects --routing
# Generowanie komponentu w bibliotece
nx generate @nrwl/angular:component my-component --project=my-library