Reputation: 119
I have a component that I'm using to display some common output to my application. This component is injected into other services so that those services can trigger the behavior. After a lot of troubleshooting I think I've tracked the issue to Angular's DI creating multiple instances of the component. I've created a bare-bones version that illustrates the issue. This is with Angular 2.4.x
AppModule:
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {AppComponent} from './app.component';
import {TestComponent} from "./test.component";
@NgModule({
declarations: [
AppComponent,
TestComponent
],
imports: [BrowserModule, FormsModule, HttpModule],
providers: [TestComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
Test Component (this is a simplified version of a component that I'm trying to use to display info and use it as a service):
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-test',
template: `<p>Message:</p>
<p *ngIf="show">hi</p>`
})
export class TestComponent {
private show: boolean = false;
doTest() {
setTimeout(() => {
console.log('timeout callback');
this.show = true;
}, 5000);
}
}
App Component (component using my Test Component):
import {Component, OnInit} from '@angular/core';
import {TestComponent} from "./test.component";
@Component({
selector: 'app-root',
template:`
<h1>{{title}}</h1>
<app-test></app-test>
`
})
export class AppComponent implements OnInit{
title = 'app works!';
constructor(private test: TestComponent) { }
ngOnInit() {
this.test.doTest();
}
}
The behavior I was hoping for is that AppComponent would call TestComponent's doTest function and the TestComponent would show the 'hi' message after 5 seconds.
The callback happens and I see the console message, but 'hi' isn't displayed. I think that's do to dependency injection providing separate instances so the instance that's injected App's constructor is different from the instance in App's template.
If my understanding is correct, how do I make it so that it's the same instance in both cases? And am I missing a better way to go about achieving this behavior?
Upvotes: 0
Views: 404
Reputation: 31873
You need to use the ViewChild
decorator to access a child component instead of injecting it into your constructor. Intuitively, what you are trying to do makes sense but it will not work.
Please note that as, Alexander Staroselsky points out in his comment, you should not list any components in your providers
array!
Here is what you need to write
import { Component, NgModule, ViewChild } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@Component({
selector: 'app-test',
template: `
<p>Message:</p>
<p *ngIf="show">hi</p>
`
})
export class AppTestComponent {
doTest() {
setTimeout(() => {
console.log('timeout callback');
this.show = true;
}, 3000);
}
show = false;
}
@Component({
selector: 'my-app',
template: '<app-test></app-test>',
})
export class App {
// this queries your view for elements of the type passed to the
// ViewChild decorator factory.
@ViewChild(AppTestComponent) test: AppTestComponent;
ngAfterContentInit() {
this.test.doTest();
}
}
@NgModule({
imports: [BrowserModule],
declarations: [App, AppTestComponent],
bootstrap: [App]
})
export class AppModule { }
Here is a working example
https://plnkr.co/edit/DDx4INrgixsnJKiU1E0h?p=preview
If you were working solely with the child component from your view, you could leave out all of the additional decorators and lifecycle hooks.
For example:
@Component({
selector: 'my-app',
template: `
<!-- give the child component a name for use in the template -->
<app-test #test>
</app-test>
<button (click)="test.doTest()">Do Test</button>
`,
})
export class App {}
Here is a working example
https://plnkr.co/edit/nnXW2z7pbgdTk5Qzl1Sw?p=preview
Upvotes: 1