src/app/features/template-export/template-export-api/template-export-api.service.ts
Interact with the PDF Template Generation API that uses File Templates to generate custom pdf documents.
| providedIn | root |
Properties |
|
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:
Parameters :
Returns :
Observable<TemplateExportBatchResult>
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 :
Returns :
Observable<TemplateExportResult>
An array buffer of the generated PDF |
| Protected getShowFileUrl | |||||||||
getShowFileUrl(entity: TemplateExport, property: string)
|
|||||||||
|
Inherited from
FileService
|
|||||||||
|
Parameters :
Returns :
string
|
| loadFile |
loadFile(entity: Entity, property: string)
|
|
Inherited from
FileService
|
|
Returns :
Observable<SafeUrl>
|
| removeAllFiles | ||||||
removeAllFiles(entity: Entity)
|
||||||
|
Inherited from
FileService
|
||||||
|
Parameters :
Returns :
Observable<any>
|
| removeFile |
removeFile(entity: Entity, property: string)
|
|
Inherited from
FileService
|
|
Returns :
Observable<any>
|
| uploadFile | ||||||||||||||||
uploadFile(file: File, entity: TemplateExport, property: string)
|
||||||||||||||||
|
Inherited from
FileService
|
||||||||||||||||
|
Upload a new template file to the API.
Parameters :
Returns :
Observable<string>
The template ID generated by the API |
| Protected reportProgress | |||||||||
reportProgress(message: string, obs: Observable
|
|||||||||
|
Inherited from
FileService
|
|||||||||
|
Parameters :
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 :
Returns :
void
|
| 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,
};
}),
);
}
}