Angular DI using injection token for model class

1.8k views Asked by At

I am trying to do something here, but not sure if it can be done. I want to create a model class like this:

import { Inject } from '@angular/core';
import {
    Element,
    Image,
} from '@situlive/situ-angular-components/contentful';

export class ImageColumn {
    title: string;
    backgroundImage: Image;
    backgroundImageAlignment: string;

    constructor(
        element: Element
    ) {
        this.title = element.fields.title;
        this.backgroundImageAlignment = element.fields.backgroundImageAlignment;
    }
}

This is so that in a component or service I can just do a new ImageColumn(element) and it will bind the properties for me. Easy so far.

Now I have a complication :) I need to bind the the backgroundImage, which is done through a service. I updated the constructor to this:

import { Inject } from '@angular/core';
import {
    ContentfulService,
    Element,
    Image,
} from '@situlive/situ-angular-components/contentful';

export class ImageColumn {
    title: string;
    backgroundImage: Image;
    backgroundImageAlignment: string;

    constructor(
        contentfulService: ContentfulService,
        element: Element
    ) {
        this.title = element.fields.title;
        this.backgroundImageAlignment = element.fields.backgroundImageAlignment;

        this.backgroundImage = this.contentfulService.createImage(
            element.fields.backgroundImage[0]
        );
    }
}

which is fine, but it causes an issue, because now anywhere where I use an ImageColumn I now have to pass in the contentfulService. I would really love to be able to pass the service without needing to supply it in the constructor.

I have done this before with services using injection tokens, so I figured I could do it here. I created an injection token like this:

import { InjectionToken } from '@angular/core';

import { ContentfulService } from '@situlive/situ-angular-components/contentful';

export const CONTENTFUL_SERVICE = new InjectionToken<ContentfulService>(
  'CONTENTFUL_SERVICE'
);

which I provided in my module like this:

@NgModule({
  imports: [
    CommonModule,
    HttpClientModule,
    RouterModule,

    AuthModule.forRoot(environment.auth0),
    ContentfulModule.forRoot(environment.contentful),
  ],
  declarations: [FooterComponent, HeaderComponent],
  exports: [FooterComponent, HeaderComponent],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: BearerInterceptor,
      multi: true,
    },
    {
      provide: CONTENTFUL_SERVICE,
      useClass: ContentfulService,
      multi: true,
    },
  ],
})
export class CoreModule {}

So I figured I would be able to do this to my model:

import { Inject } from '@angular/core';
import {
    ContentfulService,
    Element,
    Image,
} from '@situlive/situ-angular-components/contentful';

import { CONTENTFUL_SERVICE } from '../contentful-service.token';

export class ImageColumn {
    title: string;
    backgroundImage: Image;
    backgroundImageAlignment: string;

    constructor(
        @Inject(CONTENTFUL_SERVICE) private contentfulService: ContentfulService,
        element: Element
    ) {
        this.title = element.fields.title;
        this.backgroundImageAlignment = element.fields.backgroundImageAlignment;

        this.backgroundImage = this.contentfulService.createImage(
            element.fields.backgroundImage[0]
        );
    }
}

The problem is, now when i do a new ImageColumn(element) it complains that I need 2 arguments but only got 1. Does anyone know how I can get around this?

1

There are 1 answers

1
Andrei Gătej On BEST ANSWER

I think it doesn't work that way because you didn't declare any provider for the ImageColumn. Declaring the provider would let the injector know how to obtain dependencies when you inject the ImageColumn as follows:

constructor (imageColumn: ImageColumn) { } 

but since you want to instantiate it manually, I'm not sure you can let Angular know how to automatically resolve the injection token.

What I think you can do instead is to provide the Injector when you instantiate ImageColumn.

foo.component.ts

export class FooComponent {
  constructor (private injector: Injector) { }

  ngOnInit () {
    const imgColumn = new ImageColumn(element, this.injector)
  }
}

and then in your ImageColumn's constructor you can inject anything that has a provider declared, including that service:

class ImageColumn {
  constructor (private element: any, private injector: Injector) {
    this.contentfulService = this.injector.get(ContentfulService);

    /* ... */

    this.backgroundImage = this.contentfulService.createImage(
      element.fields.backgroundImage[0]
    );
  }
}