Reputation: 335
I am currently experiencing a problem using the principles of component injection in angular. I have a header component that must use in another I called WelcomePage Component. My goal is that the header component can use the methods and properties of its parents. To do this I created the following abstract class:
export abstract class WelcomeInterface {
default: any;
languagesSorted: any;
/**
* Change app language
* @param langSelect language choice
*/
abstract onChangeLang(langSelect: string): void;
}
Then the WelcomePage class implements WelcomeInterface. I also added a provider to the parent component, to allow these methods to be used by Header after its injection.
here is the Welcome-page component
@Component({
selector: 'app-welcome-page',
templateUrl: './welcomePage.component.html',
providers: [{ provide: WelcomeInterface, useClass: forwardRef(() => WelcomePageComponent) }]
})
/**
* *summary
*
* *summay
*/
export class WelcomePageComponent implements OnInit, OnDestroy, WelcomeInterface {
getPaySubscription: Subscription;
default: { value: string, flag: string };
languages = new Array<{ value: string, flag: string }>();
languagesSorted = new Array<{ value: string, flag: string }>();
isClosed: boolean;
loading: boolean;
urlJson = './assets/i18n/languages.json';
paymentMode: string;
orderId: any;
image: string;
paymentModeConstants = AppConstants.paymentModes;
routerEventSubscription: Subscription;
navEventSubscription: Subscription;
………
The problem I have is that the Header component does not see the update of the properties of WelcomePage in its lifecycle at all. Which causes full display errors in my application.
This is how I wrote the header component
import { Component, OnInit, Input, Optional } from '@angular/core';
import { WelcomeInterface } from 'src/app/models/welcomeInterface';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
constructor(@Optional() public welcomePage: WelcomeInterface) {
}
ngOnInit() {
}
}
Below the view of my Header component
<header class="header bluegreengradient">
<div *ngIf="welcomePage" class="control has-icons-left is-right">
<!-- can display if src= appConfig.settings.appLogo -->
<div class="select is-small">
<select #langSelect (change)="welcomePage.onChangeLang(langSelect.value)">
<option *ngFor="let lang of welcomePage.languagesSorted">{{lang.value}}</option>
</select>
<div class="icon is-small is-left">
<i class="flag-icon flag-icon-{{ welcomePage.default? welcomePage.default.flag:'earth'}}"></i>
</div>
</div>
</div>
<div class="container">
<h1 *ngIf="welcomePage" class="heading1">
<!-- create img source, add h1 can display if src= appConfig.settings.appLogo -->
<figure><img alt="logo" [src]="imageSrc"></figure>
</h1>
<h2 id="welcome-message" class="heading2 is-uppercase">
<!-- replace by headerTitle-->
{{ headerText | translate}}
</h2>
</div>
</header>
Upvotes: 2
Views: 303
Reputation: 214175
Your issue is that you use useClass
recipe for DI provider while should be using useExisting
.
In case of useClass
a new instance is created that has no connection to Angular views tree.
useExisting
key makes sure that your child components will get the real existing instance of your parent component.
providers: [{ provide: Interface, useExisting: forwardRef(() => AppComponent) }]
^^^^^^^^^^^
take current instance of AppComponent
Upvotes: 2
Reputation: 26432
To solve your problem, you need to change the useClass to useExisting like this.
providers: [{ provide: WelcomeInterface, useExisting: forwardRef(() => WelcomePageComponent) }]
Check stackblitz here for proof of concept. That is becaus with the use class, you provide a new object instead of the parent to the child.
This is not the right way to implement two way communication between angular components. You should use @Input
and @output
to achieve this.
import { Component, OnInit, Input, Optional } from '@angular/core';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
@Input() languages: [];
@Output() valueChange = new EventEmitter();
constructor() {
}
ngOnInit() {
}
onSelectChange(value) {
this.valueChange.emit(value);
}
}
And in your parent component
<app-header [languages]="sortedLanguages" (valueChange)="onChangeLang($event)"><app-header>
Upvotes: 1
Reputation: 1365
Your code is wrong.
In order to communicate two components you should use:
@Input
to pass data from the parent to the child and
@Output
to trigger events from the child to the parent
In case you wan't to inject some service to your component you can follow this syntax:
@Injectable()
export class MyService { ... }
and inject it into your component like this:
@Component({
...
})
export class MyComponent {
...
constructor(private myService: MyService)
}
PS: In case 'MyComponent' have a Module you have to put also your service there, for example:
@NgModule({
...
declarations: [MyComponent],
providers: [MyService}
})
export class MyModule { }
Upvotes: 0