I want to create a class decorator TopicClass
which adds a property and a function to the decorated component. The function must access a injected Service. How do I do that?
Here is what tried unsuccessfully:
@Component({
selector: 'home',
styleUrls: ['./home.component.css'],
templateUrl: './home.component.html'
})
@TopicClass('home')
export class HomeComponent {
constructor(private topicService: TopicService) { }
}
I cannot access the injected Service through the inserted function ngAfterViewInit
.
export function TopicClass(title: string) {
return function (target: Function) {
const original = target;
function construct(constructor, args) {
const c: any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
const newInstance = new c();
newInstance['topic'] = new Topic(title, '');
newInstance['ngAfterViewInit'] = () => {
newInstance['topicService'].setTopic(newInstance['topic']);
}
return newInstance;
}
const ctor: any = (...args) => {
console.log("Service: " + original.prototype.topicService);
return construct(original, args);
};
ctor.prototype = original.prototype;
return ctor;
}
}
The problem is newInstance['topicService']
is undefined.
I have set up a simple Angular project for testing: https://github.com/ptea/angular-class-decorator-test
https://github.com/ptea/angular-class-decorator-test/blob/master/src/app/services/topic.service.ts
I also tried to reproduce the problem with a simple TypeScript program, which works as intended:
newInstance['printStreet'] = () => {
console.log(`printFirstnameStreet: ${newInstance['firstname']}, ${newInstance['street']}`);
}
https://github.com/ptea/angular-class-decorator-test/blob/master/dashboard.ts
Any ideas/solution to this problem?
The reason that the TopicService
is undefined is because it was never injected into the component. With your current decorator it is redefining the constructor of the HomeComponent
to be a constructor that has no arguments. This is messing up Angular's DI and so the TopicService
is not injected when the Component is instantiated.
The best way I can think of to accomplish what you want is to not modify the constructor of the HomeComponent
and instead tap into the ngOnInit
method instead. The ngOnInit method is an ideal candidate because it is called once per the component's lifecycle and it has a fixed number of parameters, 0, which makes it easy to wrap inside another function. You can similarity do this with the ngAfterViewInit
function to use the service when desired.
Here is how you could modify the TopicClassDecorator
to acheive your desired result:
export function TopicClass(title: string) {
return function (target: Function) {
let targetNgOnInit = target.prototype.ngOnInit;
target.prototype.ngOnInit = function (){
this.topic = new Topic(title, 'subTitle');
if(targetNgOnInit){
targetNgOnInit.apply(target);
}
}
let targetNgAfterViewInit = target.prototype.ngAfterViewInit;
target.prototype.ngAfterViewInit = function (){
this.topicService.setTopic(this.topic);
if(targetNgAfterViewInit){
targetNgAfterViewInit.apply(target);
}
}
return target;
}
}
Demo Plunkr of everything working together.
I finally went with the following solution without overriding the ngOnInit function:
export function TopicClass(title: string) {
return function (target: Function) {
target.prototype.topic = new Topic(title);
let targetNgAfterViewInit = target.prototype.ngAfterViewInit;
target.prototype.ngAfterViewInit = function () {
this.topicService.setTopic(this.topic);
if(targetNgAfterViewInit){
targetNgAfterViewInit.apply(target);
}
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.