1
\$\begingroup\$

I have this angular service that is working perfectly but I'm feeling that it isn't very readable.
How would you refactor it?
(Simple tips as "try this instead of that..." are very welcome. I'm looking for different approaches.)

import { Injectable } from '@angular/core';
import { concat } from 'rxjs';
import { map, concatMap, toArray } from 'rxjs/operators';

import { EntityApiAbstration } from 'src/app/shared/abstraction-classes';
import { FeathersService } from './feathers.service';
import { UsersService } from '../state/users.service';
import { Assistencia, EventoCronologico, User } from 'src/app/shared/models';
import { lensProp, view, set, clone } from 'ramda';

@Injectable({
  providedIn: 'root'
})
export class AssistenciasApiService extends EntityApiAbstration {
  private usersAPI = this.usersService;

  private sanitize(arr: any[] | null): any[] | null {
    if (!arr) { return arr; }
    return arr.map(item => ({ id: item.id, qty: item.qty }));
  }

  private deserialize<T>(obj: T, objPropName: string) {
    const lens = lensProp(objPropName);
    if (typeof view(lens, obj) === 'string') {
      return set(lens, JSON.parse(view(lens, obj)), obj);
    }
    return obj;
  }

  private fullyDetailedAssistencia$ = (assistenciaFromApi: Assistencia) =>
    this.usersAPI.get(assistenciaFromApi.cliente_user_id)
      .pipe(
        map((res: User[]) => res[0]),
        map(cliente => (
          {
            ...assistenciaFromApi,
            cliente_user_name: cliente.nome,
            cliente_user_contacto: cliente.contacto
          } as Assistencia)
        ),

        map(assistencia => this.deserialize(assistencia, 'registo_cronologico')),
        map(assistencia => this.deserialize(assistencia, 'material')),
        map(assistencia => this.deserialize(assistencia, 'encomendas')),
        concatMap(assistencia =>
          concat(
            ...assistencia.registo_cronologico
              .map(evento =>
                this.usersAPI.get(evento.tecnico_user_id)
                  .pipe(
                    map((user: User[]) => ({ ...evento, tecnico: user[0].nome }) as EventoCronologico)
                  ))
          )
            .pipe(
              toArray(),
              map(registo_cronologico => ({ ...assistencia, registo_cronologico }) as Assistencia)
            )
        ),

        map(assistencia => {
          const registo = clone(assistencia.registo_cronologico);
          const registoFiltered = registo.filter(
            evento =>
              evento.estado === 'em análise' ||
              evento.estado === 'contacto pendente' ||
              evento.estado === 'orçamento pendente' ||
              evento.estado === 'aguarda material' ||
              evento.estado === 'concluído'
          );
          const lastEvento = registoFiltered ? registoFiltered[registoFiltered.length - 1] : null;
          const tecnico = lastEvento ? lastEvento.tecnico : null;
          return ({ ...assistencia, tecnico } as Assistencia);
        })
      )

  constructor(
    protected feathersService: FeathersService,
    private usersService: UsersService) {
    super(feathersService, 'assistencias');
  }


  find(query?: object) {
    const assistencias$ = super.find(query)
      .pipe(
        concatMap(assistencias =>
          concat(...assistencias.map(this.fullyDetailedAssistencia$))
            .pipe(toArray())
        ),
      );
    return assistencias$;
  }

  get(id: number) {
    const assistencia$ = super.get(id)
      .pipe(
        map(assistencias => assistencias[0]),
        concatMap(this.fullyDetailedAssistencia$),
        map(assistencia => [assistencia])
      );
    return assistencia$;
  }

  create(data: Assistencia, actionType?: string) {
    const assistencia$ = super.create({
      ...data,
      material: this.sanitize(data.material),
      encomendas: this.sanitize(data.encomendas)
    })
      .pipe(
        map(assistencias => assistencias[0]),
        concatMap(this.fullyDetailedAssistencia$),
        map(assistencia => [assistencia])
      );
    return assistencia$;
  }

  patch(id: number, data: Assistencia, actionType?: string) {
    const assistencia$ = super.patch(id, {
      ...data,
      material: this.sanitize(data.material),
      encomendas: this.sanitize(data.encomendas)
    })
      .pipe(
        map(assistencias => assistencias[0]),
        concatMap(this.fullyDetailedAssistencia$),
        map(assistencia => [assistencia])
      );
    return assistencia$;
  }

  onCreated() {
    const assistencia$ = super.onCreated()
      .pipe(
        map(assistencias => assistencias[0]),
        concatMap(this.fullyDetailedAssistencia$),
        map(assistencia => [assistencia])
      );
    return assistencia$;
  }

  onPatched() {
    const assistencia$ = super.onPatched()
      .pipe(
        map(assistencias => assistencias[0]),
        concatMap(this.fullyDetailedAssistencia$),
        map(assistencia => [assistencia])
      );
    return assistencia$;
  }
}

Explanation of the service: This service sits between the application and the API. fullyDetailedAssistencia$ receives an object (assistencia) from API and fetchs the API again for it's parents (basically it's trying to make a NoSQL DB behave to the application as if it was a SQL DB)

// Assistencia.model.ts
import { Artigo } from './artigo.model';
import { Encomenda } from './encomenda.model';

export interface Assistencia {
    id: number;
    cliente_user_id: number;
    cliente_user_name?: string;
    cliente_user_contacto?: number;
    registo_cronologico: EventoCronologico[]; // JSON.stringify(data: EventoCronologico[])
    tecnico?: string; // tecnico is extrated & filtered from registo_cronologico
    categoria: string;
    marca: string;
    modelo: string;
    cor: string;
    serial: string;
    problema: string;
    orcamento: number;
    relatorio_interno: string;
    relatorio_cliente: string;
    material: Partial<Artigo>[];
    encomendas: Partial<Encomenda>[];
    preco: number;
    estado: string;
    createdAt: string;
    updatedAt: string;
}

export interface EventoCronologico {
    tecnico_user_id: number;
    tecnico?: string;
    relatorio_interno?: string;
    relatorio_cliente?: string;
    material?: Partial<Artigo>[];
    encomendas?: Partial<Encomenda>[];
    preco?: number;
    estado: string;
    updatedAt: string;
}
```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.