Repository: angular
Issue: feat: ✨ Injecting EnvironmentProvider into component wrapped with @defer
Labels: area: core,core: di,needs: discussion,core: defer

### Which @angular/* package(s) are relevant/related to the feature request?

core

### Description

Greetings Team Angular 🦸‍♂️

There is sooo much goodness in Angular 17; including the migration schematics which makes upgrading as easy as upgrading your iPhone. Our entire codebase just got cleaner and better with this upgrade... and introduced a question.

We have a [router-less] Web Component story that, we think, can be improved upon.

We have a dozen components that get dynamically loaded when needed. Each component comes with its own Ngrx store. When the component gets initialized, the component talks to the store to ask for data. There is no need to load this store (EnvironmentProvider) before the component is loaded. It would just increase the main bundle size and it could end up not being used at all.

This already works in our NgModule story using a homegrown dynamic module loader. FYI It's the most advanced story we have in our Angular codebase.

With the clean way of coding when doing defer, we want to ask if there have been thoughts within the Angular team on injecting environment providers into components wrapped with defer? I know EnvironmentProviders was specifically created to avoid injecting them into components by accident. But, if Ngrx is limited to exposing their stores and effects using EnvironmentProviders, there's currently no way of lazy loading them without a route present. There's also no way to inject an Ngrx StoreModule.forFeature(): ModuleWithProviders into a component's import statement.

I have asked for a solution from @brandonroberts over at Ngrx but, for this particular issue, we are both 🤷🏾‍♂️🤷🏼.

### Proposed solution

Introduce a LazyEnvironmentProviders type that points to an array of libraries (could also support module federation) that can be lazily loaded inside defer. All the contents of @lazyProviders would get loaded before the component.

Example of defer:

```
@defer (on idle) {
   @lazyProviders {[
      '@my-org/store/lazy-provider-1',
      '@my-org/store/lazy-provider-2',
   ]}
    <inventory-map [config]="config"></inventory-map>
  } @placeholder {
    Loading...
  } @lazyProviderError {
    Dev: Cannot lazy load providers
  } @error {
    Dev: Cannot lazy load component
  }
```

The lazy module would contain an array of LazyEnvironmentProviders. This would have to be introspected, post-load, by the mechanism in charge of loading up these lazy providers.

Example of lazy provider (identical to EnvironmentProviders but represent a type that can be introspected and differentiated from the other types of providers):

```
import { OrchestratorEffects } from './+state/orchestrator/orchestrator.effects';
import * as fromOrchestrator from './+state/orchestrator/orchestrator.reducer';
import { LazyEnvironmentProviders, makeLazyEnvironmentProviders } from '@angular/core';
import { provideLazyEffects } from '@ngrx/effects';
import { provideLazyState } from '@ngrx/store';

export function provideWebComponents(): LazyEnvironmentProviders {
  return makeLazyEnvironmentProviders([
    provideLazyState(fromOrchestrator.FEATURE_KEY, fromOrchestrator.reducer),
    provideLazyEffects(OrchestratorEffects),
  ]);
}
```

### Alternatives considered

A less decoupled solution would be to allow the import of providers within the component itself. The pros of that solution is that we wouldn't have to touch how defer works at the moment. 

Instead, we would introduce a new concept into the component like lazyProviders. 

Instantiating lazyProviders before the actual component would enable us to not break state.

Example:

```
import { AsyncPipe } from '@angular/common';
import { provider1, OrchestratorFacade } from '@my-org/store/lazy-provider-1';
import { provider2 } from '@my-org/store/lazy-provider-2';

@Component({
  selector: 'inventory-map',
  templateUrl: './inventory-map.html',
  styleUrls: ['./inventory-map.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [
    AsyncPipe,
  ],
  lazyProviders: [
    provider1,
    provider2
  ]
})
export class InventoryMapComponent implements AfterLazyProviderInit {
 facade: inject(OrchestratorFacade);
 loaded$: this.facade.loaded$;

  ngAfterLazyProviderInit(providers: LazyEnvironmentProvider[]) {
    console.log('Should not be necessary if lazyProviders are instantiated before the component is.');
  }

  constructor() {}
}
```