Skip to content

Attachments ​

Uploading and downloading files attached to an entity — a front-end concern layered on top of the basic CRUD slice.

The shape ​

Attachments are themselves a small entity (entity-attachments), referenced as a child collection on the host model. The host carries an optional array; soft-deleted rows are dropped before save:

ts
import type { Entity as EntityAttachment } from "../../entity-attachments"

class Vehicle extends EntityBase {
    attachments?: Array<EntityAttachment>
    // …
}

A file is a Blob; an attachment wraps one. The entity-attachments module exports the helpers that turn files into savable attachments and round-trip them with the host (createEntity, useEntityAttachments, insertWithAttachments, updateWithAttachments, download) plus the reusable Overview component (imported as EntityAttachments) and the model Entity (imported as EntityAttachment).

The attachment service ​

A host that owns files uses the "with attachments" service variant: it overrides insert / update to round-trip files alongside the record. setup.ts needs nothing — the helpers upload through useAxios(), so the service keeps its plain AxiosInstance constructor and the default registration stands. The shape:

ts
export class EntityService extends EntityServiceBase<Entity> {
    constructor(axios: AxiosInstance, config: IConfig) {
        super(axios, config)
    }

    override insert(item) {
        // the follow-up update callback sends the attachments in display order — the server assigns SortOrder from array position
        return insertWithAttachments(
            this.config.api,
            item,
            () => super.insert(item),
            (saved) => super.update(saved)
        )
    }
    override update(item) {
        return updateWithAttachments(this.config.api, item, () => super.update(item))
    }
}

A method that moves the bytes itself — posting a file with upload, fetching one with getFile — needs the file-aware AxiosWithFilesInstance from vue/http. (Endpoints that only read or write JSON, like the advanced example's getAttachments(so?), do not: this.axios.get is enough.) Call useAxios() inside the method to reach them: EntityServiceBase declares protected axios: AxiosInstance, so this.axios stays narrow in the subclass whatever the constructor takes. The slice has no single-file save helper, so an endpoint that adds one file posts it directly and maps the response:

ts
async addAttachment(itemId: number, file: Blob): Promise<EntityAttachment> {
    const {
        data: { item },
    } = await useAxios().upload(`${this.config.api}/${itemId}/files`, [file]) // field name "file"
    return EntityAttachment.create(item)
}

The base prepareItem only strips top-level _-prefixed keys — it does not filter soft-deleted children, so override it to drop _deleted attachments (and other owned rows) before save — see services.md for prepareItem / processItem.

Upload / download UI — file fields ​

In the form, a dedicated files tab binds the host's attachments array to the shared EntityAttachments overview (imported from entity-attachments):

html
<EntityAttachments v-model="item.attachments" :readonly="readonly" />

That component owns the add/remove/preview interactions; you do not hand-roll the file <input>. Wire it into a tab alongside the main form.

Download URLs ​

Downloads go through the file-aware axios from vue/http, not a bare <a href>: AxiosWithFilesInstance exposes getFile(url, method?, filename?, type?) (returns a Blob) and upload(url, files, options?). Resolve the instance with useAxios() and build the URL from config.api (e.g. {api}/{id}/files).

When you need it ​

Only complex entities that own files. The simple/standard slices have none of the above — no file fields and the default insert/update. Reach for the attachments variant only when the entity actually carries files, and keep everything else identical to those slices.

Overview ​

  1. Abstractions
  2. Services
  3. Config
  4. Views
  5. Built-in features
  6. Attachments
  7. Checklist