How to approach separation of layers mapping response to model in Angular app? I want to organize mapping for my Angular app so that code structure would clear for other developers and I want to have as less code as possible.
I have an api service that is responsible for communicating with API. I create separate data services, which utilize the api service, to get backend data. The same data can be used in different features. I want to provide the data to some feature. But the feature needs a more complex model than backend provides. So, I want to do a complex mapping specific for the particular feature which extends the backend model. But I also do a basic mapping that is required for every feature like property naming ('_name' to 'name') or type converting (string to Date). What is your recommendation on how to approach this?
My current idea is the following. I put the data services in the app/core folder and do the basic mapping on this level. I have feature related models and mapper services in the app/feature folders. It looks OK in terms of separation of concerns. But I have doubts about the number of mapping functions and services. Looks wordy, doesn't it?
app/core/lesson
lesson-dto.model.ts
export interface LessonDto {
    id: string;
    _departmentId: string;
    begin: string;
    end: string;
}

get-lessons-response.model.ts
export interface GetLessonsResponse {
    lessons: LessonDto[];
}

lesson.model.ts
export interface Lesson {
    id: string;
    departmentId: string;
    begin: Date;
    end: Date;
}

lesson-basic-mapping.ts
export function mapLessonDtoToLesson(lessonDto: LessonDto): Lesson {
    return {
        id: lessonDto.id,
        departmentId: lessonDto._departmentId,
        begin: new Date(lessonDto.begin),
        end: new Date(lessonDto.end)
    };
}

lesson.data.service.ts
export class LessonDataService {

    constructor(private readonly apiService: ApiService) { }

    public getLessons(
        departmentId: string
    ): Observable&lt;Lesson[]&gt; {
        const requestUrl = `/departments/${departmentId}/lessons`;

        return this.apiService.get(requestUrl).pipe(
            map((response: GetLessonsResponse) =&gt; response.lessons
                .map(mapLessonDtoToLesson))
        );
    }
}

app/features/schedule
schedule-lesson.model.ts
export interface ScheduleLesson {
    id: string;
    name: string;
    begin: Date;
    end: Date;
    description: string;
}

scheduler-mapping.ts
export function mapLessonToScheduleLesson(lesson: Lesson): ScheduleLesson {
    return {
        id: lesson.id,
        name: calculateName(lesson.id, lesson.departmentId),
        begin: new Date(lessonDto.begin),
        end: new Date(lessonDto.end),
        description: doSomeOtherComplexMappingHere(lesson.departmentId)
    };
}

lesson-mapped-data.service.ts
export class LessonMappedDataService {

    constructor(private readonly lessonDataService: LessonDataService) { }

    public getLessons(
        departmentId: string
    ): Observable&lt;ScheduleLesson[]&gt; {
        return this.lessonDataService.get(departmentId).pipe(
            map((lessons: Lesson[]) =&gt; lessons
                .map(mapLessonToScheduleLesson))
        );
    }
}

One more thing that concerns me is that the more models I have or the longer they are the more similar code I have. I did some analysis. I like the approach with decorators. It looks similar to Newtonsoft in C# and hides the implementation of basic mapping. But it requires some hacky solutions to align it with TypeScript. And the feature of decorators is still unclear. So, I don't use it.
Do you have any ideas on how to make the code better (more clear and less to read)? And what do you think of the file/folder structure?
Thank you in advance.
I made some good experience with the following approach. Its more code, but it helped the other developers in the team (and especially the new ones) to get a good grip and a fast start.
As always, many roads, choose the one that fits best in your context, they all have drawbacks. :-)
First i describe the expected data from the Backend
export interface AddressJSON{
  street: string,
  city: string
};

export interface UserJSON{
  id: number,
  name: string,
  address: AddressJSON
}

That helps to understand what to expect from the backend. Also its easier to track mapping problems (for example a Java LONG that is stored in a JavaScript NUMBER =&gt; Number has a smaller value range)
Then i create the FE models and if the mapping between backend and FE is easy, then i add converter methods
class Address {
  public street: string;
  public city: string;

  constructor(street: string, city: string){
    this.street = street;
    this.city = city;
  }

  public toJSON(): AddressJSON{
    return {
      street: this.street,
      city: this.city
    }
  }

  public static fromJSON(address: AddressJSON): Address{
    return new Address(address.street, address.city);
  }
}


class User {
  public name: string;
  public address: Address;
  private readonly id: number;

  constructor(id:number){
    this.id = id;
  }

  public toJSON(): UserJSON {
    return {
      id: this.id,
      name: this.name,
      address: this.address.toJSON()
    }
  }

  public static fromJSON(user: UserJSON): User{
    const newUser: User = new User(user.id);
    newUser.name = user.name;
    newUser.address = Address.fromJSON(user.address);
    return newUser;
  }
}

As you can see the entities can be stacked.
One reason i choose classes over interfaces for my entities is that i can move functionality directly to the entities.
And, i can FORCE the way this entity has to be used. For example i can disallow the creation of a artificial user without an id.
It reduces the risc for later changes. And it enforces a deeper understanding of the business requirements behind those entities.
On the over side its much more work at the start.
Most people who didn´t liked this approach hated the extra work, that you have to invest. The people who liked it, mostly highlighted the better understanding of the business requirements and the possibly to enforce correct usage.