Detail
The <smart-detail> component renders a single model field value by delegating to a sub-component chosen by FieldType. It selects a sub-component based on the FieldType from an internal baseMap and an optional extendMap injected via DETAIL_FIELD_COMPONENTS_TOKEN. It renders the label (via ModelLabelPipe), the value (NgComponentOutlet on the selected sub-component), or a skeleton when item() returns undefined.
Usage
<smart-detail [type]="type" [options]="nameOptions" />
<smart-detail [type]="type" [options]="emailOptions" />
Components
DetailComponent (<smart-detail>)
Main wrapper component. Selects a sub-component from baseMap (or extendMap override) based on options().options.type and renders it via NgComponentOutlet. Shows a skeleton placeholder while item() is undefined.
DetailBaseComponent (abstract)
Abstract base directive for all detail sub-components. Provides:
options: InputSignal<IDetailOptions<T>>— field configurationcssClass: InputSignal<string>(aliasclass) — external CSS classesafterSetOptionsHandler()— hook invoked via effect on options change- Effect that calls
ChangeDetectorRef.detectChanges()when options change
Default Sub-Components
| FieldType | Sub-component | Selector | Description |
|---|---|---|---|
text | DetailTextComponent | smart-detail-text | Text via ListCellPipe + TrustHtmlPipe, fallback for unknown types |
email | DetailEmailComponent | smart-detail-email | Link mailto: |
enum | DetailEnumComponent | smart-detail-enum | Single value or array with translate |
flag | DetailFlagComponent | smart-detail-flag | Inline SVG ✓ / ✗ |
color | DetailColorComponent | smart-detail-color | Rectangle with background-color |
address | DetailAddressComponent | smart-detail-address | IAddress (street, number, zip, city) |
object | DetailObjectComponent | smart-detail-object | Nested model via DETAILS_COMPONENT_TOKEN |
array | DetailArrayComponent | smart-detail-array | Array of nested models |
dateRange | DetailDateRangeComponent | smart-detail-date-range | start – end |
phoneNumberPl | DetailPhoneNumberPlComponent | smart-detail-phone-number-pl | Link tel:48... as badge |
image | DetailImageComponent | smart-detail-image | <img> via FileService.getUrl(item[key].id) |
logo | DetailLogoComponent | smart-detail-logo | <img> with item[key] as URL |
video | DetailVideoComponent | smart-detail-video | <video controls> via FileService |
attachment | DetailAttachmentComponent | smart-detail-attachment | Native <button> Tailwind — download via FileService |
pdf | DetailPdfComponent | smart-detail-pdf | Native <button> Tailwind — show via FileService |
API
DetailComponent Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<IDetailOptions<T> | undefined> | required | Field configuration |
type | InputSignal<any> | required | Model class (used by ModelLabelPipe) |
IDetailOptions
IFieldOptions (relevant properties): type?: FieldType, info?: string.
DETAIL_FIELD_COMPONENTS_TOKEN
InjectionToken<Partial<Record<FieldTypeDef, Type<DetailBaseComponent<any>>>>> — allows substituting a sub-component for any field type.
Maps are merged ({ ...baseMap, ...extendMap }), so only selected types need to be overridden.
Preline field presets
Fifteen field types ship a Preline-styled preset (Detail<Field>PresetComponent, selector smart-detail-<field>-preset) alongside the default sub-component, living in <field>/preset/:
| FieldType | Preset | Look |
|---|---|---|
email | DetailEmailPresetComponent | mailto: link, blue + hover:underline, envelope icon |
enum | DetailEnumPresetComponent | Values as soft blue badges (reuses badge preset recipes) |
flag | DetailFlagPresetComponent | Soft badge — green ✓ / red ✗ (reuses badge preset recipes) |
color | DetailColorPresetComponent | size-6 rounded swatch + monospace hex code |
address | DetailAddressPresetComponent | Multi-line text-sm block with pin icon |
dateRange | DetailDateRangePresetComponent | start – end as two soft gray chips |
phoneNumberPl | DetailPhoneNumberPlPresetComponent | tel: link styled as a soft blue badge |
logo | DetailLogoPresetComponent | <img> max-h-10 object-contain |
image | DetailImagePresetComponent | 150×150 preview, rounded-xl, border + shadow-2xs |
video | DetailVideoPresetComponent | <video controls> framed rounded-xl border + shadow-2xs |
attachment | DetailAttachmentPresetComponent | File chip: icon, file name (fileName/name), download |
pdf | DetailPdfPresetComponent | File chip: red PDF icon, file name, show button |
text | DetailTextPresetComponent | text-sm typography, text-pretty; em-dash when empty |
object | DetailObjectPresetComponent | Card envelope around the untouched nested details |
array | DetailArrayPresetComponent | space-y-2 stack of item cards; em-dash when empty |
Apply them via the ready-made partial map:
Notes:
- The map is partial — all other field types keep their standard components (maps merge over
baseMap). Registeringtextin the map does NOT change the fallback for unmapped/unknown types — the map is keyed perFieldType, so those still fall back to the standardDetailTextComponent. - Unlike their standard components, the
textandarraypresets render an em-dash placeholder when the value is empty (the standard components collapse to nothing). - The presets have a fixed look;
IDetailOptionscarries no per-field style channel (deliberate, matches the fidelity-gap deferral from the Preline group).
Extending the base class
import {
ChangeDetectionStrategy,
Component,
computed,
signal,
} from '@angular/core';
import {
DetailBaseComponent,
DetailComponent,
DETAIL_FIELD_COMPONENTS_TOKEN,
IDetailOptions,
} from '@smartsoft001/angular';
import { Field, FieldType, Model } from '@smartsoft001/models';
/**
* A custom renderer for one detail field, built on `DetailBaseComponent`.
*
* The base supplies the `options` input (key, item signal, field metadata) and
* the `class` input, and re-runs `afterSetOptionsHandler()` whenever the
* options change. Everything else is the implementation's own template.
*/
@Component({
selector: 'docs-custom-detail-text',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<p [class]="classes()">{{ value() }}</p>`,
})
export class CustomDetailTextComponent extends DetailBaseComponent<
Record<string, unknown>
> {
value = computed(() => {
const options = this.options();
const item = options?.item?.();
if (!options || !item) return '';
return String(item[options.key] ?? '');
});
// `cssClass` is left aliased as `class` by the base — smart-detail forwards
// the consumer's class under that name, so do not redeclare it here.
classes = computed(() =>
['docs-detail-text', this.cssClass()].filter(Boolean).join(' '),
);
}
@Model({})
class ApplicantModel {
@Field({ details: true, type: FieldType.text })
name = 'Margot Foster';
}
/**
* `<smart-detail>` chooses the renderer from the field's `type`, so a custom
* implementation is registered per FieldType through
* `DETAIL_FIELD_COMPONENTS_TOKEN`. Entries are merged over the built-in map,
* so only the listed types change.
*/
@Component({
selector: 'docs-detail-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [DetailComponent],
providers: [
{
provide: DETAIL_FIELD_COMPONENTS_TOKEN,
useValue: { [FieldType.text]: CustomDetailTextComponent },
},
],
template: `
<smart-detail
[type]="type"
[options]="options"
[class]="'docs-detail-text--demo'"
/>
`,
})
export class DetailCustomExampleComponent {
type = ApplicantModel;
options: IDetailOptions<ApplicantModel> = {
key: 'name',
options: { type: FieldType.text },
item: signal(new ApplicantModel()),
};
}
Source
The component lives in packages/shared/angular/src/lib/components/detail and is documented for Claude Code by the angular-components-detail skill.