src/app/features/template-export/template-export-api/template-export-api.service.ts

Description

Interact with the PDF Template Generation API that uses File Templates to generate custom pdf documents.

Extends

FileService

Metadata

Relationships

Index

Properties
Methods

Methods

generateBatchFromTemplate
generateBatchFromTemplate(template: TemplateExport, dataList: Object[], mode: "zip" | "combined")

Generate output from one template for many records in a single request.

Both modes go through the template engine's native batch rendering — the backend just forwards the request:

  • mode: "zip" (default): N independent files packaged in a ZIP archive.
  • mode: "combined": N rendered files merged into a single multi-page PDF.
Parameters :
Name Type Optional Default value Description
template TemplateExport No

The TemplateExport entity to render

dataList Object[] No

The array of data objects (typically entities) to apply to the template

mode "zip" | "combined" No "zip"

How the backend should aggregate the output

The generated file (ZIP or PDF) and a derived filename

generatePdfFromTemplate
generatePdfFromTemplate(template: TemplateExport, data: Object)

Generate a PDF applying actual data to an existing template.

Parameters :
Name Type Optional Description
template TemplateExport No

The TemplateExport entity to render

data Object No

The data object (typically an entity) to be applied to the template

An array buffer of the generated PDF

Protected getShowFileUrl
getShowFileUrl(entity: TemplateExport, property: string)
Inherited from FileService
Parameters :
Name Type Optional
entity TemplateExport No
property string No
Returns : string
loadFile
loadFile(entity: Entity, property: string)
Inherited from FileService
Parameters :
Name Type Optional
entity Entity No
property string No
Returns : Observable<SafeUrl>
removeAllFiles
removeAllFiles(entity: Entity)
Inherited from FileService
Parameters :
Name Type Optional
entity Entity No
Returns : Observable<any>
removeFile
removeFile(entity: Entity, property: string)
Inherited from FileService
Parameters :
Name Type Optional
entity Entity No
property string No
Returns : Observable<any>
uploadFile
uploadFile(file: File, entity: TemplateExport, property: string)
Inherited from FileService

Upload a new template file to the API.

Parameters :
Name Type Optional Description
file File No

to be uploaded to the API as a template

entity TemplateExport No
property string No
Returns : Observable<string>

The template ID generated by the API

Protected reportProgress
reportProgress(message: string, obs: Observable)
Inherited from FileService
Parameters :
Name Type Optional
message string No
obs Observable<HttpEvent | any> No
Returns : void
showFile
showFile(entity: Entity, property: string)
Inherited from FileService

If a file is available, downloads this file and shows it in a new tab.

Parameters :
Name Type Optional Description
entity Entity No
property string No

where a file previously has been uploaded

Returns : void

Properties

Readonly API_URL
Type : unknown
Default value : environment.API_PROXY_PREFIX + "/v1/export"
Protected alertService
Type : unknown
Default value : inject(AlertService)
Inherited from FileService
Protected dialog
Type : MatDialog
Default value : inject(MatDialog)
Inherited from FileService
Protected entities
Type : unknown
Default value : inject(EntityRegistry)
Inherited from FileService
Protected entityMapper
Type : unknown
Default value : inject(EntityMapperService)
Inherited from FileService
Protected httpClient
Type : HttpClient
Default value : inject(HttpClient, { optional: true, })
Inherited from FileService
Protected schemaService
Type : unknown
Default value : inject(EntitySchemaService)
Inherited from FileService
Protected snackbar
Type : MatSnackBar
Default value : inject(MatSnackBar)
Inherited from FileService
Protected syncState
Type : unknown
Default value : inject(SyncStateSubject)
Inherited from FileService
import { inject, Injectable } from "@angular/core";
import { FileService } from "../../file/file.service";
import { SafeUrl } from "@angular/platform-browser";
import { Entity } from "app/core/entity/model/entity";
import { Observable, of, throwError } from "rxjs";
import { HttpResponse } from "@angular/common/http";
import { NotAvailableOfflineError } from "../../../core/session/not-available-offline.error";
import { NAVIGATOR_TOKEN } from "../../../utils/di-tokens";
import { switchMap } from "rxjs/operators";
import { TemplateExport } from "../template-export.entity";
import { Logging } from "../../../core/logging/logging.service";
import { environment } from "../../../../environments/environment";
import {
  TemplateExportComplement,
  TemplateExportContextService,
} from "../template-export-context/template-export-context.service";

/**
 * Format of API response body upon uploading a new template file.
 */
interface TemplateUploadResponseDto {
  templateId: string;
}

/**
 * Format of API request body to render a PDF from a template.
 * TemplateId is provided via URL path.
 */
interface TemplateRenderRequestDto {
  /**
   * target file type (e.g. "pdf")
   */
  convertTo: string;

  /**
   * The data used to fill placeholders in the template.
   */
  data: Object;

  /**
   * Additional context data available in the template under the `{c.…}` prefix.
   */
  complement?: TemplateExportComplement;
}

/**
 * Format of API request body to render a batch of files from one template.
 * `data` is an array of records; the backend will render each one and return a ZIP.
 */
interface TemplateRenderBatchRequestDto {
  convertTo: string;
  data: Object[];

  /**
   * Additional context data, shared by all records of the batch,
   * available in the template under the `{c.…}` prefix.
   */
  complement?: TemplateExportComplement;
}

export interface TemplateExportResult {
  filename: string;
  file: ArrayBuffer;
}

export interface TemplateExportBatchResult {
  filename: string;
  file: ArrayBuffer;
}

/**
 * Interact with the PDF Template Generation API that uses File Templates to generate custom pdf documents.
 */
@Injectable({
  providedIn: "root",
})
export class TemplateExportApiService extends FileService {
  private navigator = inject<Navigator>(NAVIGATOR_TOKEN);
  private readonly exportContext = inject(TemplateExportContextService);

  readonly API_URL = environment.API_PROXY_PREFIX + "/v1/export";

  /*
  --- FileService methods ---
  */

  /**
   * Upload a new template file to the API.
   * @param file to be uploaded to the API as a template
   * @param entity
   * @param property
   * @return The template ID generated by the API
   */
  uploadFile(
    file: File,
    entity: TemplateExport,
    property: string,
  ): Observable<string> {
    if (!this.navigator.onLine) {
      return throwError(() => new NotAvailableOfflineError("File Attachments"));
    }

    const formData = new FormData();
    formData.append("template", file, file.name);

    return this.httpClient.post(this.API_URL + "/template", formData).pipe(
      switchMap(async (res: TemplateUploadResponseDto) => {
        entity.templateId = res.templateId;
        await this.entityMapper.save(entity);
        return res.templateId;
      }),
    );
  }

  protected override getShowFileUrl(
    entity: TemplateExport,
    property: string,
  ): string {
    return this.API_URL + "/template/" + entity.getId();
  }

  loadFile(entity: Entity, property: string): Observable<SafeUrl> {
    // should not be required for our use cases of the Template Export API
    throw new Error("Method not implemented.");
  }

  removeFile(entity: Entity, property: string): Observable<any> {
    // we do not do explicit file removal due to the design of the API
    Logging.debug("skipping file removal for Template Export API");
    return of(true);
  }

  removeAllFiles(entity: Entity): Observable<any> {
    Logging.debug("skipping file removal for Template Export API");
    return of(true);
  }

  /*
  --- PDF Generation API methods ---
  */

  /**
   * Generate a PDF applying actual data to an existing template.
   * @param template The TemplateExport entity to render
   * @param data The data object (typically an entity) to be applied to the template
   * @return An array buffer of the generated PDF
   */
  generatePdfFromTemplate(
    template: TemplateExport,
    data: Object,
  ): Observable<TemplateExportResult> {
    const complement = this.exportContext.getComplement();

    return this.httpClient
      .post(
        this.API_URL + "/render/" + template.getId(),
        {
          convertTo: "pdf",
          data: data,
          ...(complement ? { complement } : {}),
        } as TemplateRenderRequestDto,
        { observe: "response", responseType: "arraybuffer" },
      )
      .pipe(
        switchMap(async (res: HttpResponse<ArrayBuffer>) => {
          // the API returns the filename in the Content-Disposition header as a URL-encoded string with special delimiters
          const filenameMatch = decodeURIComponent(
            res.headers.get("Content-Disposition"),
          ).match(/filename="(.+)"/);

          const fileName =
            filenameMatch && filenameMatch.length > 1
              ? filenameMatch[1]
              : template.title;

          return {
            filename: fileName,
            file: res.body,
          };
        }),
      );
  }

  /**
   * Generate output from one template for many records in a single request.
   *
   * Both modes go through the template engine's native batch rendering — the backend
   * just forwards the request:
   * - `mode: "zip"` (default): N independent files packaged in a ZIP archive.
   * - `mode: "combined"`: N rendered files merged into a single multi-page PDF.
   *
   * @param template The TemplateExport entity to render
   * @param dataList The array of data objects (typically entities) to apply to the template
   * @param mode How the backend should aggregate the output
   * @return The generated file (ZIP or PDF) and a derived filename
   */
  generateBatchFromTemplate(
    template: TemplateExport,
    dataList: Object[],
    mode: "zip" | "combined" = "zip",
  ): Observable<TemplateExportBatchResult> {
    const fallbackExtension = mode === "combined" ? ".pdf" : ".zip";
    const complement = this.exportContext.getComplement();

    return this.httpClient
      .post(
        this.API_URL + "/render-batch/" + template.getId() + "?mode=" + mode,
        {
          convertTo: "pdf",
          data: dataList,
          ...(complement ? { complement } : {}),
        } as TemplateRenderBatchRequestDto,
        { observe: "response", responseType: "arraybuffer" },
      )
      .pipe(
        switchMap(async (res: HttpResponse<ArrayBuffer>) => {
          const disposition = res.headers.get("Content-Disposition");
          const filenameMatch = disposition
            ? decodeURIComponent(disposition).match(/filename="?([^";]+)"?/)
            : null;
          const filename =
            filenameMatch && filenameMatch.length > 1
              ? filenameMatch[1]
              : template.title + fallbackExtension;

          return {
            filename,
            file: res.body,
          };
        }),
      );
  }
}

results matching ""

    No results matching ""