Przejdź do treści

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ń:

src/assets/i18n/
├── de.json
├── en.json
└── pl.json

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:

nx graph

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

Dalsze zasoby