Open Closed

Angular - Extensible Form Template — How to Access Full Property Context in Custom Field Component #9399


User avatar
0
dcsimoes created

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:

  • When using the default ABP rendering, the options function for typeahead and other extensible fields receives a rich PropData object (with record and sometimes helpers like getInjected).
  • However, in my custom template component, propData is just the record (row/entity) and does not include any extra context or helper functions.
  • This means that my field’s options function has less information than it does in the default scenario.

Questions:

  1. Is there a supported way to access the full PropData (with helpers and full context) inside a custom template/component (e.g., for use in options or validators)?
  2. Is it possible to have EXTENSIONS_FORM_PROP_DATA provide the full context instead of only the record when using template overrides?
  3. If not, is there a recommended pattern to pass additional context to custom field templates, so options/validators can use other injected services or helpers as in the default renderer?
  4. Are there plans to support this use-case in a future version?

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



1 Answer(s)
  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Expert

    Hello David,

    Thank you for the detailed ticket and context around your use of custom templates with the extensible forms. Let me address your questions as well.

    When using custom components inside extensible forms or tables, the injected EXTENSIONS_FORM_PROP_DATA only provides the plain record by default, not the full PropData with helpers like getInjected.

    Although PropData defines getInjected(...), it's only populated in certain rendering contexts (e.g., the default ABP flow). When creating custom components, this context isn’t automatically passed.

    If you're overriding rendering via custom components (like template: MyComponent inside prop config), you're responsible for providing the full context (record + helpers) if needed.

    {
      provide: EXTENSIONS_FORM_PROP_DATA,
      deps: [Injector],
      useFactory: (injector: Injector) => ({
        record,
        getInjected: <T>(token: any) => injector.get<T>(token)
      })
    }
    

    As for future support, we’ll share feedback with the team — but for now, the pattern above is the recommended way to ensure full context is available in custom components.

    I am also sharing a sample project showing how I replicated your structure based on these details: https://drive.google.com/file/d/1qLoFopObla_kGQ1k_a71KVvrlMZ_DPBc/view?usp=drive_link

    If you think that this does not answer your question, I can assist you further. Thank you for your cooperation.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.3.0-preview. Updated on June 13, 2025, 11:37