agnidé
agnidé

Reputation: 335

Angular components injection issues

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

Answers (3)

yurzui
yurzui

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

Stackblitz Example

Upvotes: 2

Athanasios Kataras
Athanasios Kataras

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

Pedro Bezanilla
Pedro Bezanilla

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

Related Questions