question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Choose injector when creating components

See original GitHub issue

🚀 feature request

Relevant Package

@angular/core

Description

Often, I’d like to inject different values into different components. For example, have a component tree with color: 'BLUE injected, and another component tree with color: 'RED' injected.

This sort of desire comes up in a lot of different forms. Sometimes the component tree exist contemporaneously; other times they don’t (e.g. router-like functionality).

The point of dependency injection is to not code against fixed values, however Angular’s DI currently makes it cumbersome to adjust the injector.

Describe the solution you’d like

Specify the injector when creating the component. Example syntax:

export class RootComponent {
  constructor(private readonly injector: Injector) {}
  readonly blueProviders: StaticProviders[] = [{ provide: COLOR, useValue: 'BLUE' }];
  readonly blueInjector = Injector.create(this.blueProviders, this.injector);
}
<my-component [@injector]="blueInjector"></my-component>

or

<my-component [@providers]="blueProviders"></my-component>

Describe alternatives you’ve considered

  1. Inject an Observable. However that complicates client code, and requires components to understand how to handle changes.

  2. Use non-template code to dynamically create components. However, lifecycles methods, etc. make the approach complex in general.

  3. Use *ngComponentOutlet. However, like (2), that removes the ability to use inputs with the child component, and it requires explicitly adding components to module entry points.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:3
  • Comments:15 (11 by maintainers)

github_iconTop GitHub Comments

2reactions
pauldrapercommented, Apr 1, 2021

A solution that allows injection of dynamic view values (doesn’t allow specifying the Injector, but can inject values). It does not create DOM noise.

<ng-container [exampleValue]="color1"> <!-- inject color1 -->
  <leaf *exampleRender example></leaf>
</ng-container>

The exampleValue directive would be superfluous if it were not for the feature regression in Angular 4 (#15998) where template directives aren’t DI parents for the template.

Full example
import { BrowserModule } from "@angular/platform-browser";
import {
  Component,
  Directive,
  Input,
  TemplateRef,
  NgModule,
  VERSION,
  ViewContainerRef,
  EmbeddedViewRef,
  InjectionToken,
  Inject
} from "@angular/core";
import { Subject } from "rxjs";
import { distinctUntilChanged, scan } from "rxjs/operators";

export const EXAMPLE = new InjectionToken<string>("EXAMPLE");

@Directive({ selector: "[exampleValue]" })
export class ExampleValueDirective {
  value: string;
  readonly value$ = new Subject();

  @Input() set exampleValue(value: string) {
    this.value = value;
    this.value$.next(value);
  }
}

@Directive({ selector: "[exampleRender]" })
export class ExampleRenderDirective {
  constructor(
    private readonly template: TemplateRef<{}>,
    private readonly exampleValue: ExampleValueDirective,
    private readonly viewContainer: ViewContainerRef
  ) {
    this.render.subscribe();
  }

  private readonly render = this.exampleValue.value$.pipe(
    distinctUntilChanged(),
    scan(
      view => {
        if (view) {
          view.destroy();
        }
        return this.viewContainer.createEmbeddedView(this.template);
      },
      <EmbeddedViewRef<{}> | undefined>undefined
    )
  );
}

export function getExample(directive: ExampleValueDirective) {
  return directive.value;
}

@Directive({
  selector: "[example]",
  providers: [
    {
      provide: EXAMPLE,
      useFactory: getExample,
      deps: [ExampleValueDirective]
    }
  ]
})
export class ExampleDirective {}

@Component({
  selector: "leaf",
  template: `
    Injected value #{{ n }}: {{ example }}
  `
})
export class LeafComponent {
  constructor(@Inject(EXAMPLE) readonly example: string) {}

  @Input() n: number;
}

@Component({
  selector: "app",
  template: `
    <div [exampleValue]="color1">
      <leaf *exampleRender example [n]="1"></leaf>
    </div>
    <div [exampleValue]="color2">
      <leaf *exampleRender example [n]="2"></leaf>
    </div>
    <div>
      <button type="button" (click)="toggle()">Toggle</button>
    </div>
    <hr />
    Angular version: {{ VERSION.full }}
  `
})
export class AppComponent {
  readonly VERSION = VERSION;

  color1 = "blue";
  color2 = "red";

  toggle() {
    const tmp = this.color1;
    this.color1 = this.color2;
    this.color2 = tmp;
  }
}

@NgModule({
  imports: [BrowserModule],
  declarations: [
    AppComponent,
    ExampleDirective,
    ExampleValueDirective,
    ExampleRenderDirective,
    LeafComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

https://stackblitz.com/edit/angular-25tawh

2reactions
Airbladercommented, Jul 1, 2020

@djleonskennedy A wrapping component creates DOM noise and CSS burden, though, and making it dynamic to have something like

<use-injector [injector]="blueInjector">
  <my-component></my-component>
</use-injector>

doesn’t work since use-injector has to statically define its providers and cannot reference them from an input. So you’d have to create different wrapper components for every injector, which then becomes problematic if you build your injector somewhat dynamically as well (which I think is what @pauldraper wants to do).

My first thought here was a structural directive like

<my-component *useInjector="blueInjector"></my-component>

But this actually turns out not to really lead anywhere either.

Edit: The above confuses injectors and providers a bit, but I think it’s still clear enough. In the end these are separate issues for injectors and providers since wrapping components can define providers, but not injectors for their content.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Hierarchical injectors - Angular
With hierarchical dependency injection, you can isolate sections of the application and give them their own private dependencies not shared with the rest...
Read more >
Inject Objects into your Components with Angular ...
To inject an object, you will have to create your own DI token. You will then be able to inject your object using...
Read more >
How to Implement Services and Dependency Injection in ...
Services are wired together using a mechanism known as Dependency Injection (DI). We just need to have an injectable service class to be...
Read more >
Provide custom injector to dynamically created external + AOT ...
How can I properly provide a custom injector to a component which comes from an external resource? angular · Share.
Read more >
Component Injection - Vespa Documentation
a dependent consumer,; a declaration of a component's dependencies,; an injector that creates instances of classes that implement a given dependency on request....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found