Which @angular/* package(s) are relevant/related to the feature request?
core
Description
Currently, providing the same token across multiple directives on the same element does not guarantee which instance will be used.
https://angular.dev/guide/di/defining-dependency-providers#component-or-directive-providers
NOTE: If multiple directives on the same element provide the same token, one will win, but which one is undefined.
I would like to be able to rely on some level of precedence. Specifically, I would like to stabilize the current behavior, where directives applied to a component host in the template will override the providers on that component.
This is extremely useful for component libraries, because it allows us to use providers on a component to define default behavior that can be overridden by directives applied to the same component.
Example: a date input component. Such a component may define a service to determine how a text value is parsed into a date. Some date fields may require a time component, some may only care about the month and year. The base component can define a behavior for the most common case, and provide additional directives to change the date format.
@Component({
selector: 'app-date-input',
providers: [
{
provide: DATE_FORMAT,
useClass: DefaultDateFormat,
},
],
})
export class DateInputComponent {
private readonly format = inject(DATE_FORMAT, { self: true })
}
@Directive({
selector: '[appDateTime]',
providers: [
{
provide: DATE_FORMAT,
useClass: DateTimeFormat,
},
],
})
export class DateTimeDirective {}
<!-- this uses DateTimeFormat, guaranteed by Angular -->
<app-date-input appDateTime />
Proposed solution
Define a precedence order for providers on the same element. My proposal:
- Directives applied in the template win. If multiple directives applied this way provide the same token, it is undefined which wins as it is today.
<!-- providers from someDirective win here -->
<some-component someDirective />
- The component's host directives win next.
- Component providers used as the final fallback.
I'm not 100% certain whether 2 or 3 should be swapped (or merged). If component providers win over host directives, it allows the component to override a provider on one of its host directives. However I am not sure if implementing it that way might create performance problems or require significant refactoring. It may make sense to keep that undefined as well.
This would allow a component to define "default" behaviors via a token or service implementation in its providers, or through its host directives. An application using this component can override these behaviors by providing a new implementation via a directive.
Alternatives considered
The main alternative is to not provide the token in the component providers, and instead construct the default implementation if the token is not provided.
// Get the format for this date field.
// Imagine this service is stateful, so it needs to be component-local.
inject(DATE_FORMAT, { self: true, optional: true }) ?? new DefaultDateFormat()
This has a few problems:
- Constructing services with
new is an anti-pattern in the DI system.
- The token is not available to sub-components or directives.
- The token is not available to
viewChild queries in the parent component.
Which @angular/* package(s) are relevant/related to the feature request?
core
Description
Currently, providing the same token across multiple directives on the same element does not guarantee which instance will be used.
https://angular.dev/guide/di/defining-dependency-providers#component-or-directive-providers
I would like to be able to rely on some level of precedence. Specifically, I would like to stabilize the current behavior, where directives applied to a component host in the template will override the providers on that component.
This is extremely useful for component libraries, because it allows us to use providers on a component to define default behavior that can be overridden by directives applied to the same component.
Example: a date input component. Such a component may define a service to determine how a text value is parsed into a date. Some date fields may require a time component, some may only care about the month and year. The base component can define a behavior for the most common case, and provide additional directives to change the date format.
Proposed solution
Define a precedence order for providers on the same element. My proposal:
I'm not 100% certain whether 2 or 3 should be swapped (or merged). If component providers win over host directives, it allows the component to override a provider on one of its host directives. However I am not sure if implementing it that way might create performance problems or require significant refactoring. It may make sense to keep that undefined as well.
This would allow a component to define "default" behaviors via a token or service implementation in its providers, or through its host directives. An application using this component can override these behaviors by providing a new implementation via a directive.
Alternatives considered
The main alternative is to not provide the token in the component providers, and instead construct the default implementation if the token is not provided.
This has a few problems:
newis an anti-pattern in the DI system.viewChildqueries in the parent component.