3

When running the following code as-is, it get this error:

Uncaught TypeError: Cannot read property 'TYPE_CAMPAIGN' of undefined

Plunker Example

entity.service.ts

import {Injectable} from '@angular/core';
import {CampaignService} from './campaign/campaign.service';
import {TagService} from './tag/tag.service';

@Injectable()
export class EntityService {
  static TYPES = [
    CampaignService.TYPE_CAMPAIGN,
    TagService.TYPE_TAG
  ];
}

campaign.service.ts

import {Injectable} from '@angular/core';
import {EntityService} from '../entity.service';

@Injectable()
export class CampaignService {
  static TYPE_CAMPAIGN = 'campaign';

  constructor(private entityService: EntityService) {}

  public getTypes() {
    return EntityService.TYPES;
  }
}

tag.service.ts

import {Injectable} from '@angular/core';
import {EntityService} from '../entity.service';

@Injectable()
export class TagService {
  static TYPE_TAG = 'tag';

  constructor(private entityService: EntityService) {}
}

However, when I remove the constructor from campaign.service.ts, the code works without issue. Why does the error occur, and how do I access static properties when the constructor is included?

Update 1: After testing using Angular's Injector to offset when classes are loaded, I find that I still have an issue accessing static properties of EntityService inside methods of CampaignService. I've also found that adding private entityService: EntityService in campaign.service.ts causes the issue.

Update 2: The issue is caused by the order of import statements in the Module that is providing the services (I recently alphabetized my import statements).

app.module.ts

import {CampaignService} from './campaign/campaign.service';
import {EntityService} from '../entity.service';
import {TagService} from './tag/tag.service';

@NgModule({
  providers: [
    CampaignService,
    EntityService,
    TagService
  ]
});

When the import statement for entity.service.ts is moved before campaign.service.ts, both CampaignService and TagService are able to work without issue.

Update 3: It looks like the issue is version specific. Here is the example of the issue happening in the version I am currently using: Example

If you switch the import statements in src/app.component.ts while watching the console, then you will see the issue at hand.

Myzaree
  • 55
  • 5

1 Answers1

2

It may be the injector decides there's a circular reference. Looking at the code, I would not expect it since only one service has a constructor. However, the behavior you describe points to this.

You can try injecting the injector and delaying the injection process by a tick.
See Angular2: 2 services depending on each other

@Injectable()
export class CampaignService {
  static TYPE_CAMPAIGN = 'campaign';
  private entityService;

  constructor(injector: Injector) {
    setTimeout(() => this.entityService = injector.get(EntityService));
  }
}

Using EntityService.TYPES inside campaign.service.ts

Hopefully this covers your use-case. Here's my StackBlitz

campaign.service.ts

import {Injectable, Injector} from '@angular/core';
import {EntityService} from './entity.service';

@Injectable()
export class CampaignService {
  static TYPE_CAMPAIGN = 'campaign';
  private entityService;
  types = EntityService.TYPES;

  constructor(private injector: Injector) {
    setTimeout( () => this.entityService = injector.get(EntityService) );
  }
}

app.component.ts

import { Component } from '@angular/core';
import {EntityService} from './entity.service';
import {CampaignService} from './campaign.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular 5';
  types = EntityService.TYPES;

  constructor(
    private entityService: EntityService, 
    private campaignService: CampaignService
  ) { }
}

app.component.html

<hello name="{{ name }}"></hello>
<p>
  Start editing to see some magic happen :)
</p>
<div> Types from EntityService {{ types }} </div>
<div> Types from campaignService {{ campaignService.types }} </div>
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • Thank you! I'm sure it has something to do with circular references, but this one is throwing me through a loop. The Injector strategy does allow me to use methods/properties inside EntityService, as long as they are not static properties. This means I still have the same issue when I try `EntityService.TYPES` inside `campaign.service.ts`. – Myzaree Jan 06 '18 at 02:39
  • I have a StackBlitz testbed, will see if I can get my head around it - I don't often use statics! :) – Richard Matsen Jan 06 '18 at 02:54
  • So far I have `types = EntityService.TYPES;` in app.component.ts, and it prints both types to the template. – Richard Matsen Jan 06 '18 at 02:56
  • Wow, I just found what is causing the issue... I alphabetized my import statements in the first instance I imported EntityService and CampaignService into a Module. – Myzaree Jan 06 '18 at 03:03
  • I would still love to know how to not have this issue in the future, aside from a general restructuring of the application. Its strange that the import order matters in this case... – Myzaree Jan 06 '18 at 03:05
  • I have to say, alphabetizing doesn't sound like a remedy. – Richard Matsen Jan 06 '18 at 03:09
  • Could you reproduce it in a fork of the stackblitz please. Would like to check it out. – Richard Matsen Jan 06 '18 at 03:10
  • So, I think the injecting has nothing to do with it (I just removed all that injector stuff and it still works). Since the types are all static, best guess is you just had a bum reference in the import. Alphabetizing them has just led you to tidy them up. – Richard Matsen Jan 06 '18 at 03:16
  • The issues looks to be version specific. Unfortunately I am tied to an older version of Angular for the time being. I attached the example to the main question. – Myzaree Jan 06 '18 at 04:29