2

My application displays data about different kinds of business objects in the same standard component.

That is, let's assume there is an Angular component type EditorComponent<T extends object>. This component is instantiated using a component outlet that receives a custom injector initialized with providers that are specified to each business object type in an interface like this:

interface IBusinessObjectInfo {
  readonly providers?: StaticProvider[]
}

The concrete business object types are supplied by individual business modules (that the framework must not know about). Thus, for each business object type, something like this is done:

boInfo: IBusinessObjectInfo = createBusinessObjectInfo({
  objectRestrictions: { ... },
  ...
});

where createBusinessObjectInfo accepts an interface like this:

interface ITypedBusinessObjectInfo<T extends object> {
  readonly objectRestrictions: IObjectRestrictions<T>;
  ...
}

That is, the object passed to createBusinessObjectInfo is generically typed to the business object type.

Now, it's the responsibility of the createBusinessObjectInfo function to generate providers based on the supplied object info that are equipped with appropriately typed injection tokens.

Within EditorComponent<T>, then, I want to inject those objects, and also instantiate some other classes from the framework that work on business objects and are generically typed to them. For example:

@Component...
class EditorComponent<T extends object> {

  public constructor() {
    this.restrictionViewModel = new RestrictionViewModel<T>(this.objectRestrictions);
  }
  
  public readonly restrictionViewModel: RestrictionViewModel<T>;
  
  private readonly objectRestrictions: IObjectRestrictions<T> = inject(...);
}

So far, so good.

The question is: Where/how do I get my generically typed injection tokens from?

I could define them without any typing:

export const objectRestrictionToken = new InjectionToken('object restrictions');

Then, I could cast them upon use, or - slightly more convenient to write - hand them out only using a function:

export function getObjectRestrictionToken<T extends object>(): InjectionToken<IObjectRestrictions<T>> {
  return <InjectionToken<IObjectRestrictions<T>>>objectRestrictionToken;
}

Obviously, this solution with an explicit typecast of an existing object instance is not so nice. Nor is it nice that I have to explicitly specify T when calling the getObjectRestrictionToken<T> function.

Is there any other way, without damaging the separation between (generic) framework and concrete business modules?

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
F-H
  • 663
  • 1
  • 10
  • 21
  • The problem is that [NgComponentOutlet](https://github.com/angular/angular/blob/938d7a65f65e77b65c0d40919de97547f0305b3a/packages/common/src/directives/ng_component_outlet.ts#L75) is not generic and doesn't even make an effort to capture the type of the component passed to it. Angular can be confusing in this respect because of things like `export interface Type extends Function {new(...args: any[]): T;} export const Type = Function;`. We could imagine `class NgComponentOutlet {@Input() ngComponentOutlet: Type; @Input() injector: GenInjector; }` but alas it's not provided – Aluan Haddad Aug 04 '23 at 14:22
  • @AluanHaddad: Indeed, the component outlet is not generic, and it cannot check that the component created and the static providers supplied to it actually fit together. This can only be ensured by thorough coding in this delicate part of the framework, which is indeed protected by the fact that it is very localized and doesn't have to be touched by many people. The magic happens inside the component, where it doesn't matter that the class is not actually instantiated with a generic parameter at runtime (which gets erased away), but where the compiler will ensure I all the code inside the ... – F-H Aug 04 '23 at 15:31
  • ... component fits together based on the generic type parameter declared at design time. Likewise, the compiler will ensure that the object passed to `createBusinessObjectInfo` is consistently typed to the entity type. (Obviously, one could abuse that and create one's own `IBusinessObjectInfo`, but this will very likely become apparent immediately as the wrong stuff is in the providers and it should be rejected during code review. – F-H Aug 04 '23 at 15:33

0 Answers0