x)- [X] bug report -> please search issues before submitting
- [ ] feature request
@angular/cli: 1.4.4
node: 8.5.0
os: darwin x64
@angular/animations: 4.4.4
@angular/common: 4.4.4
@angular/compiler: 4.4.4
@angular/core: 4.4.4
@angular/forms: 4.4.4
@angular/http: 4.4.4
@angular/platform-browser: 4.4.4
@angular/platform-browser-dynamic: 4.4.4
@angular/router: 4.4.4
@angular/upgrade: 4.4.4
@angular/cli: 1.4.4
@angular/compiler-cli: 4.4.4
typescript: 2.5.3
I created the following service (which gets extended) which works fine in ng serve
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
// models
import {Question} from "../_models/question"
// environment
import {environment} from '../../environments/environment';
@Injectable()
abstract class ApiService {
protected area;
protected get URL() {
return environment.apiURL + '/api/v2/' + this.area;
}
constructor(protected httpClient: HttpClient) {
}
public questions() {
return this.httpClient.get(this.URL + '/questions');
}
public getQuestion(id: string) {
return this.httpClient.get(this.URL + '/questions/' + id);
}
public tags(extended: boolean = false) {
if (extended) {
return this.httpClient.get(this.URL + '/tags/extended');
} else {
return this.httpClient.get(this.URL + '/tags');
}
}
public updateQuestion(question: Question) {
return this.httpClient.put(this.URL + '/questions/' + question._id, question);
}
public createQuestion(data: Question) {
return this.httpClient.post(this.URL + '/questions/question', data);
}
public deleteQuestion(id: string) {
return this.httpClient.delete(this.URL + '/questions/' + id);
}
public searchQuestions(terms) {
return this.httpClient.post(this.URL + '/questions/search', terms);
}
public count() {
return this.httpClient.get(this.URL + '/questions/count');
}
}
export class ExamService extends ApiService {
protected area = 'exam';
}
export class LessonService extends ApiService {
protected area = 'lesson';
}
usage:
import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {ExamService} from '../_services/api.service';
@Component({
templateUrl: 'dashboard.component.html'
})
export class DashboardComponent {
public examStats: any = {};
constructor(private examService: ExamService) {
}
ngOnInit() {
this.examService.count()
.subscribe((data: { total: number }) => {
this.examStats.count = data.total;
});
}
}
When I run the same exact code via prod ng serve --prod I get the following error on the service:
vendor.9ead56f8ae9a0e9bc69c.bundle.js:1 ERROR TypeError: Cannot read property 'get' of undefined
at e.t.tags (0.12dca0b4bedd46aac615.chunk.js:1)
at l.UH1D.l.ngOnInit (6.d5388f69c020eaa085d0.chunk.js:1)
at Mn (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at hr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at cr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at Hr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at Object.updateDirectives (6.d5388f69c020eaa085d0.chunk.js:1)
at Object.updateDirectives (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at lr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at Mr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
and when I go to the specific line of compiled code that triggers this is:
...?this.httpClient.get(this.URL+"/tags/extended"):this.httpClient.get(this.URL+"/tags")},t.prototype...
vendor.9ead56f8ae9a0e9bc69c.bundle.js:1 ERROR TypeError: Cannot read property 'get' of undefined
at e.t.tags (0.12dca0b4bedd46aac615.chunk.js:1)
at l.UH1D.l.ngOnInit (6.d5388f69c020eaa085d0.chunk.js:1)
at Mn (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at hr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at cr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at Hr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at Object.updateDirectives (6.d5388f69c020eaa085d0.chunk.js:1)
at Object.updateDirectives (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at lr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
at Mr (vendor.9ead56f8ae9a0e9bc69c.bundle.js:1)
I suspect that this is bug in current AOT implementation. I'd expect them to behave the same way.
I was able to overcome the issue in prod mode by declaring the construct in the extended class but it will then break in non-prod mode with the following error:
export class ExamService extends ApiService {
constructor(httpClient: HttpClient) {
super(httpClient);
}
protected area = 'exam';
}
ERROR Error: Uncaught (in promise): Error: Can't resolve all parameters for ExamService: (?).
Error: Can't resolve all parameters for ExamService: (?).
Shouldn't this behave the same regardless of the environment?
You need the @Injectable() decorator on ExamService, not the abstract ApiService.
So after moving @Injectable() before each export it now works in both environments. the construct is still needed in all extends, is that intentional?
You need @Injectable() on each concrete class that you a providing to the injector. You do not need it on any abstract base classes. And you do not need to write a constructor in a derived class unless you are adding parameters.
EDIT: This is not correct as discussed below. You either need to provide the derived constructor or decorate the base class with @Injectable(). Sorry for the confusion.
@kevinphelps Not really. If there's no @Injectable on base class but there is @Injectable on derived class this will result in injector error.
I have several abstract base classes in my code and I only need @Injectable() on the derived concrete classes. It does not result in an injector error.
I actually have abstract classes that extend abstract classes. And only the concrete classes need to be decorated with @Injectable(). This makes perfect sense because Angular only needs to know what dependencies are needed for the concrete class it is instantiating. It does not care what its base classes need. I have base classes whose constructors take in parameters that aren't provided by the dependency injection system at all. Angular doesn't care because it doesn't need to know.
If I don't include the constructor in the concrete class I get errors in both envionrments
@Injectable()
export class ExamService extends ApiService {
//constructor(httpClient: HttpClient) {
// super(httpClient);
//}
protected area = 'exam';
}
ng serve
ERROR Error: Uncaught (in promise): Error: Can't resolve all parameters for ExamService: (?).
ng serve --prod
ERROR TypeError: Cannot read property 'get' of undefined
@kevinphelps This will work. This won't. It's not about constructor params. It's about param annotation. @Injectable makes use of TypeScript metadata emitting feature. It has to be specified on a class where metadata was specified in constructor (which is abstract class), so it could be be emitted by the decorator. This is how it will be done in JIT. I'm not totally sure what happens in AOT.
Okay, that is correct. I thought I had code where I left out the derived constructor. But it seems that I don't. Sorry for the confusion.
You either need to provide the derived constructor or decorate the base class with @Injectable().
I do have code that leaves out the constructor in the derived class, but the base class is decorated with @Injectable().
Still not working in production. I don't mind redefining the constructor in each but just want to confirm this is the intended behavior
@Injectable()
abstract class ApiService {
...
}
@Injectable()
export class ExamService extends ApiService {
protected area = 'exam';
}
ng serve --prod:
ERROR TypeError: Cannot read property 'get' of undefined
That should work with AOT. I have services where the abstract base class and the derived class are both decorated @Injectable() and the constructor is only defined in the abstract base class. I don't use ng serve, but it definitely works with the AOT compiler.
This seems like an issue related to the usage of extended classes for DI and not for the CLI, closing this issue.
@Brocco I wonder what unit this issue belongs to then? Angular compiler? Because it obviously looks like a bug. DI has no problems with it, as it was shown with JIT example.
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._