Search code examples
angulartypescriptabstract-classangular-componentsangular-dependency-injection

Abstract method implementation in component has undefined dependencies


Abstract class:

export abstract class LanguageChangeAware {

    private sub: Subscription;
    protected language: string;

    protected constructor(protected eventService: EventService) {
        this.sub = eventService.getLang().subscribe(lang => {
            this.language = lang;
            this.load();
        });

        this.load();
    }

    protected ngOnDestroy(): void {
        this.sub.unsubscribe();
    }

    protected abstract load();
}

Component NewsPage implements LanguageChangeAware abstract class:

export class NewsPage extends LanguageChangeAware {

    public news: Array<NewsItem>;

    constructor(public newsService: NewsService, protected eventService: EventService) {
        super(eventService);
    }

    protected load() {
        console.log('NewsPage - load() - ', this.newsService); // <- undefined

        this.newsService.getNewsItemsList().then(data => {
            this.news = data;
        });
    }
}

My problem is that in the load() implementation of the NewsPage component, the injected dependency NewsService is undefined.


One possible solution, suggested by user Antoine Boisier-Michaud, was to make the subscription inside the ngOnInit method.

Updated version of LanguageChangeAware:

export abstract class LanguageChangeAware implements OnInit, OnDestroy {

    private sub: Subscription;
    protected language: string;

    protected constructor(protected eventService: EventService) {
    }

    public ngOnInit(): void {
        this.sub = this.eventService.getLang().subscribe(lang => {
            this.language = lang;
            this.load();
        });

        this.load();
    }

    public ngOnDestroy(): void {
        this.sub.unsubscribe();
    }

    protected abstract load();

}

Solution

  • The constructor should be used for initializing class members and for dependency injection. If you have initialization stuff you need to do, you should use the ngOnInit method. When ngOnInit is called, you know all the dependencies have been resolved.

    export abstract class LanguageChangeAware implements OnInit {
    
        private sub: Subscription;
        protected language: string;
    
        constructor(protected eventService: EventService) { }
    
        protected ngOnInit(): void {
            this.sub = eventService.getLang().subscribe(lang => {
                this.language = lang;
                this.load();
            });
    
            this.load();
        }
    
        protected ngOnDestroy(): void {
            this.sub.unsubscribe();
        }
    
        protected abstract load();
    }
    

    You can read angular's documentation on lifecycles if you want to learn more about OnInit and other lifecycle hooks.

    You can also read this article which discusses when to use OnInit and the constructor more specifically.