RuslanPr0g/Hiscaries

🚀 FRONTEND: Standardize Model Mapping Layers for Form, Domain, and API Communication

Open

#127 opened on May 23, 2025

 (0 comments) (1 reaction) (1 assignee)C# (0 forks)auto 404
enhancementhelp wanted

Repository metrics

Stars
 (2 stars)
PR merge metrics
 (PR metrics pending)

Description

Summary

We currently have several representations of models scattered across reactive forms, domain logic, and API integration. There is inconsistency in how these models are translated, leading to fragile code, duplication, and tight coupling between layers. This issue proposes introducing a structured, layered model mapping approach.

Problem

  • Mixing of API and domain models in components and services.
  • Reactive forms are directly bound to raw API objects.
  • Lack of standardized mappers introduces duplication and inconsistent transformations.
  • Difficult to scale or refactor form structures or API payloads independently.

Proposal

Introduce a clear separation of models and centralized mappers for each transformation step.

Layers

  • Form Model: Used in Angular Reactive Forms (FormGroup) File: {entity-name}-form.model.ts
  • Domain Model: Used in internal logic / UI ({entity-name} class) File: {entity-name}-domain.model.ts
  • API Models: Input/output contract with backend Files: {entity-name}-api-read.model.ts, {entity-name}-api-request.model.ts

Mappers

Use stateless utility classes (pure functions) to transform between models.

Mapper Example:

// story.mapper.ts

export class StoryMapper {
  static fromApiToDomain(api: StoryApiReadModel): StoryDomainModel
  static fromDomainToForm(domain: StoryDomainModel): StoryFormModel
  static fromFormToDomain(form: StoryFormModel): StoryDomainModel
  static fromDomainToRequest(domain: StoryDomainModel): StoryUpdateRequest
}

Usage in Component:

// Loading for Edit
this.storyService.getById(id).pipe(
  map(StoryMapper.fromApiToDomain),
  tap(domain => {
    const formModel = StoryMapper.fromDomainToForm(domain);
    this.form.patchValue(formModel);
  })
);

// Saving
onSubmit() {
  const formModel = this.form.getRawValue();
  const domainModel = StoryMapper.fromFormToDomain(formModel);
  const request = StoryMapper.fromDomainToRequest(domainModel);
  this.storyService.update(request).subscribe();
}

Benefits

  • Separation of concerns — Forms, business logic, and API layers are decoupled.
  • Scalability — Changes in backend contracts or form structure don’t leak into other layers.
  • Testability — Mappers can be tested independently with full coverage.
  • Consistency — Reduces bugs from improper or inconsistent data transformations.

Priority

High — This change lays foundational structure needed for upcoming feature expansion.

Contributor guide