Hello ABP Team,
I am working with ABP extensible forms, and I’m using a custom template/component to override the rendering of certain fields via the [templates]
property on <abp-extensible-form>
. My custom component injects the field config and record data using:
constructor(
@Inject(EXTENSIONS_FORM_PROP) public prop: FormProp,
@Inject(EXTENSIONS_FORM_PROP_DATA) public propData: any
) {}
My issue/question:
PropData
object (with record
and sometimes helpers like getInjected
).propData
is just the record (row/entity) and does not include any extra context or helper functions.options
function has less information than it does in the default scenario.Questions:
PropData
(with helpers and full context) inside a custom template/component (e.g., for use in options or validators)?EXTENSIONS_FORM_PROP_DATA
provide the full context instead of only the record
when using template overrides?Any best practices, workarounds, or guidance would be greatly appreciated.
Thank you!
@Component({
selector: 'custom-typeahead-field',
template: ` ... `
})
export class CustomTypeaheadFieldComponent {
constructor(
@Inject(EXTENSIONS_FORM_PROP) public prop: FormProp,
@Inject(EXTENSIONS_FORM_PROP_DATA) public propData: any
) {}
// ...
search = (params: PagedAndSortedResultRequestDto): Observable<PagedResultDto<any>> => {
if (this.prop?.options) {
return this.prop.options(this.propData, params).pipe(
switchMap((result: any) => {
if (Array.isArray(result)) {
return of({ items: result, totalCount: result.length });
}
return of(result);
})
);
}
return of({ items: [], totalCount: 0 });
};
}
{
type: ePropType.Typeahead,
name: 'propId',
displayName: 'propName',
validators: () => [],
options: (data$, params) => {
const manufacturerService = data$.getInjected(MyService);
return myService
.getList({
filter: params ?? '',
maxResultCount: params?.maxResultCount,
skipCount: params?.skipCount,
})
.pipe(
map(result => {
//add empty value
return result.items.map(item => ({ value: item.id, key: item.name }));
})
);
},
template: MyWrapperComponent,
},
**ERROR TypeError: data$.getInjected is not a function**
Thank you, David Simões
We have analyzed the source code for ExtensibleTableComponent
(specifically the ngOnChanges
method where the injector for *ngComponentOutlet
is created). We found that the injector provided to the custom component only includes a provider for the PROP_DATA_STREAM
token. This token provides anObservable<string>
representing the formatted display value of the cell, not the raw record object for the row.
(Reference: ExtensibleTableComponent.ts -> ngOnChanges -> Injector.create({...}) block
// Snippet from ExtensibleTableComponent.ts showing the limited injector creation
if (prop.value.component) {
record[propKey].injector = Injector.create({
providers: [
{
provide: PROP_DATA_STREAM, // Only provides formatted value stream
useValue: value, // 'value' is Observable<string> from getContent
},
// PROBLEM: Missing provider for the 'record' object or a context token
],
parent: this.#injector,
});
record[propKey].component = prop.value.component;
}
Impact: Because the record data is not injected, custom components rendered via the component property cannot access the row-specific data they need to function correctly. This prevents the creation of interactive cell components that depend on row context.
Enhancement Request:
Could the ExtensibleTableComponent
be enhanced to provide the row record object (or a context object containing it) to custom components via the injector? The standard approach would be to use a documented InjectionToken (e.g., ROW_RECORD
, COMPONENT_CONTEXT
, or similar) for this purpose.
// Example of desired injector enhancement
Injector.create({
providers: [
{ provide: PROP_DATA_STREAM, useValue: value },
{ provide: ROW_RECORD, useValue: record } // <-- Requesting this
],
parent: this.#injector,
});
This would allow developers to build stateful, interactive components that integrate properly with the table row data, significantly enhancing the extensibility of the table.
I've created an issue on ABP repository regarding this situation: https://github.com/abpframework/abp/issues/22555
We also observed some difficulty in reliably setting very narrow fixed column widths (e.g., 10-30px) using columnWidth when a custom component is rendered, although wider widths seem to apply correctly. This might be a separate minor issue related to CSS or internal calculations.
What if I need the checkbox to appear before the actions column?
Sorry for insisting on this matter, but I still couldn't understand how to achieve that.
Can you give an example for CustomMultiSelectComponent
?
I'm working with the abp-extensible-table
component and have a requirement to allow users to select multiple lines from it within a specific column. Currently, it seems like it doesn't support it.
Could you please provide guidance or suggest a way to implement a multi-select within a column of the abp-extensible-table? Any examples or best practices for achieving this would be greatly appreciated.
Hello, thank you for reporting this issue.
However, I am unable to see your HTML code properly. Could you please resend it?
Hi. I updated the question with the HTML code.
Created an component with the following HTML using abp-extensible-form:
<ng-template #abpBody>
<ng-template #loaderRef>
<div class="text-center"><i class="fa fa-pulse fa-spinner"></i></div>
</ng-template>
<form [formGroup]="form" (ngSubmit)="save()" [validateOnSubmit]="true">
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template>
And with the following typeahead defined on defaults:
{
type: ePropType.Typeahead,
name: 'myProp',
id: 'myProp',
displayName: '::MyField',
validators: () => [Validators.required],
options: (data, params) => {
const service = data.getInjected(MyService);
return service
.getList({
filter: params ?? '',
maxResultCount: params?.maxResultCount,
skipCount: params?.skipCount,
} as MyInput)
.pipe(
map(pagedResult => {
return pagedResult.items.map(
item =>
({
key: item.displayName,
value: item.id,
})
);
})
);
},
}
When creating a new record everything works fine, but when I patch a value to the form wich contains myProp
correctly defined and with a valid value the typeahead component don't autofill that value, even when trying to force it through form. I don't have this problem with abp-lookup-typeahead
components in forms created manually through HTML.
Here's my buildform method
buildForm() { const data = new FormPropData(this.injector, this.selected); this.form = generateFormFromProps(data); }
Thanks in advance for any help provided.