src/app/core/common-components/record-id-display/record-id-display.component.ts
The internal id of a record, shown quietly and copyable in one click.
A record's title identifies it only by convention: records generated from one template share theirs (every event of a recurring activity), so the id is what actually tells two apart. It is also what the change log's related-record filter takes, and what a technical administrator needs for troubleshooting.
Deliberately understated: readers looking for a record by name should not have their attention pulled to a string that means nothing to them.
| changeDetection | ChangeDetectionStrategy.OnPush |
| selector | app-record-id-display |
| standalone | true |
| imports |
MatTooltipModule
|
| styleUrls | ./record-id-display.component.scss |
| template | |
| styleUrl | ./record-id-display.component.scss |
MatTooltipModule
Properties |
|
Methods |
Inputs |
| recordId | |
Type : string
|
|
|
the record's full id, e.g. |
|
| copyId |
copyId()
|
|
Copy the id, confirming only once the clipboard actually accepted it: a browser may refuse, and silently claiming success would send someone off to paste something they do not have. A refusal can also come as a thrown error rather than a
Returns :
void
|
import {
ChangeDetectionStrategy,
Component,
inject,
input,
} from "@angular/core";
import { Clipboard } from "@angular/cdk/clipboard";
import { MatTooltipModule } from "@angular/material/tooltip";
import { AlertService } from "../../alerts/alert.service";
import { Logging } from "../../logging/logging.service";
/**
* The internal id of a record, shown quietly and copyable in one click.
*
* A record's title identifies it only by convention: records generated from one
* template share theirs (every event of a recurring activity), so the id is what
* actually tells two apart. It is also what the change log's related-record
* filter takes, and what a technical administrator needs for troubleshooting.
*
* Deliberately understated: readers looking for a record by name should not have
* their attention pulled to a string that means nothing to them.
*/
@Component({
selector: "app-record-id-display",
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatTooltipModule],
template: `
@if (recordId()) {
<span
class="record-id truncate-text pointer clickable"
role="button"
tabindex="0"
[attr.aria-label]="copyHint"
[matTooltip]="copyHint"
(click)="$event.stopPropagation(); copyId()"
(keydown.enter)="copyId()"
(keydown.space)="$event.preventDefault(); copyId()"
>{{ recordId() }}</span
>
}
`,
styleUrl: "./record-id-display.component.scss",
})
export class RecordIdDisplayComponent {
private readonly clipboard = inject(Clipboard);
private readonly alerts = inject(AlertService);
/** the record's full id, e.g. `Child:5e69d648-...` */
readonly recordId = input<string>();
protected readonly copyHint = $localize`:Record id copy hint:This is the internal unique ID for this record. Copy this for advanced analysis or as context to help your technical administrators with troubleshooting.`;
/**
* Copy the id, confirming only once the clipboard actually accepted it: a
* browser may refuse, and silently claiming success would send someone off to
* paste something they do not have.
*
* A refusal can also come as a thrown error rather than a `false` return, so
* both are treated the same. Letting it propagate would surface as an
* unhandled error from a click the user only meant as a copy.
*/
copyId() {
const id = this.recordId();
if (!id) {
return;
}
let copied = false;
try {
copied = this.clipboard.copy(id);
} catch (err) {
Logging.debug("could not copy the record id", err);
}
if (copied) {
this.alerts.addInfo($localize`:Record id copied:Record ID copied`);
}
}
}
./record-id-display.component.scss
/* Hug the id text.
This element is often a flex item (the change log's record cell, the history
dialog's subtitle), where a stretched host leaves dead space beside the id:
it looks like part of the id line but carries no copy handler, so a click
there falls through to whatever the row does instead of copying. */
:host {
align-self: flex-start;
max-width: 100%;
min-width: 0;
}
/* understated on purpose: a reader looking for a record by name should not have
their attention pulled to a string that means nothing to them */
.record-id {
display: inline-block;
max-width: 100%;
font-size: 85%;
opacity: 0.75;
}