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<Lesson[]> {
const requestUrl = `/departments/${departmentId}/lessons`;
return this.apiService.get(requestUrl).pipe(
map((response: GetLessonsResponse) => 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<ScheduleLesson[]> {
return this.lessonDataService.get(departmentId).pipe(
map((lessons: Lesson[]) => 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.