Angular 5. Ketergantungan melingkar di TypeScript saat mengimpor kelas satu sama lain


Dalam aplikasi Angular 5 dengan TypeScript. Saya menghadapi masalah yang disebut Circular Dependency ketika mencoba mengimplementasikan komunikasi antara komponen. Ada dua komponen radio dan radio-group:

<radio-group [(value)]='selected'>
  <radio value='1'></radio>
  <radio value='2'></radio>
  <radio value='3'></radio>
</radio-group>

Mereka berkomunikasi satu sama lain saat pengguna memilih dan membatalkan pilihan item.

Contoh implementasi komponen RadioGroupComponent:

import { Component, forwardRef, Input, Optional, ContentChildren, 

QueryList, EventEmitter, Output, ChangeDetectorRef, ChangeDetectionStrategy, AfterContentInit, OnChanges } from '@angular/core';
import { RadioGroup } from './radio-group.component';

@Component({
  selector: 'radio',
  styles: [`:host{cursor:pointer;}`],
  template: `<div (click)='check()'>
  <span *ngIf='!checked'>⚪️</span>
  <span *ngIf='checked'>🔘</span>
  <span>Click me. Value: {{value}} Checked: {{checked}}</span>
  </div>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Radio  {
  @Input() value: any;
  checked = false;
  private _radioGroup: RadioGroup;
  constructor(@Optional() radioGroup: RadioGroup, public cd: ChangeDetectorRef){
    this._radioGroup = radioGroup;
  }
  check(){
    this.checked = true;
    if(this._radioGroup){
      this._radioGroup.selected = this;
    }
    this.markForCheck();
  }
  markForCheck(){
    this.cd.markForCheck();
  }
}

KomponenRadio :

import { Component, forwardRef, Input, Optional, ContentChildren, QueryList, EventEmitter, Output, ChangeDetectorRef, ChangeDetectionStrategy, AfterContentInit, OnChanges } from '@angular/core';
import { Radio } from './radio.component';

@Component({
  selector: 'radio-group',
  template: `<ng-content></ng-content>`,
})
export class RadioGroup implements AfterContentInit, OnChanges{
  set selected (component:Radio){
    this._selected = component;
    this.valueChange.emit(component.value);
  } 
  private _selected:Radio = null;
  @Input() value:any;
  @Output() valueChange = new EventEmitter();
  @ContentChildren(forwardRef(() => Radio)) radioComponents: QueryList<Radio>;

  ngAfterContentInit() { this.checkParentComponents();}
  ngOnChanges(){ this.checkParentComponents();}
  checkParentComponents():void{
    this.radioComponents 
    && this.radioComponents.forEach(item=>{
        item.checked = item.value==this.value;
        if(item.checked){ this._selected = item;}
        item.markForCheck();
    });
  }
}

Contoh daring

Contoh kerja dengan semua deklarasi dalam satu file (stackblitz.com)

Contoh rusak dengan file terpisah (stackblitz.com)

Masalah

Bagaimana saya bisa menyelesaikan masalah ketergantungan melingkar ini dan menaruh semua komponen serta implementasinya ke dalam file terpisah? Seiring waktu komponen menjadi berat, bagaimana saya bisa memecahnya menjadi bagian-bagian?

Jawaban #1

Anda tidak boleh mengedit atribut RadioGroup dari Radio. Komunikasi antara komponen anak dan induk sebaiknya dilakukan melalui @Input dan @Output.

Jadi hapus RadioGroup dari konstruktor Radio. Sebagai gantinya, Anda dapat melakukan hal berikut,

import { 
   Component, 
   Input,
   EventEmitter,
   Output,
   ChangeDetectorRef,
   ChangeDetectionStrategy
} from '@angular/core';

@Component({
  selector: 'radio',
  styles: [`:host{cursor:pointer;}`],
  template: `
     <div (click)='check()'>
        <span *ngIf='!checked'>⚪️</span>
        <span *ngIf='checked'>🔘</span>
        <span>Click me. Value: {{value}} Checked: {{checked}}</span>
     </div>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Radio  {
  @Input() value: any;
  @Output() valueChange: EventEmitter<any> = new EventEmitter();
  checked = false;
  constructor(public cd: ChangeDetectorRef){
  }
  check(){
    this.checked = true;
    this.valueChange.emit(this.value);
    this.markForCheck();
  }
  markForCheck(){
    this.cd.markForCheck();
  }
}
RadioGroup.component
@Component({
  selector: 'radio-group',
  template: `<ng-content></ng-content>`,
})
export class RadioGroup implements AfterContentInit, OnChanges, OnDestroy {
  set selected(component: Radio) {
    this._selected = component;
    this.valueChange.emit(component.value);
  }
  private _selected: Radio = null;
  @Input() value: any;
  @Output() valueChange = new EventEmitter();
  @ContentChildren(forwardRef(() => Radio)) radioComponents: QueryList<Radio>;

  subscriptionList = [];

  ngAfterContentInit() { this.checkParentComponents(); }
  ngOnChanges() { this.checkParentComponents(); }
  checkParentComponents(): void {
    if (this.radioComponents) {
      this.subscriptionList = this.radioComponents.map(item => {
        item.checked = item.value === this.value;
        if (item.checked) { this._selected = item; }
        item.markForCheck();
        // subscribe to each child "valueChange" event and return these subscriptions.
        return item.valueChange.subscription(value => this.selected = value);
      });

    }
  }

  ngOnDestroy() {
      // don't forget to unsubscribe.
      if (this.subscriptionList && this.subscriptionList.length ) {
          this.subscriptionList.forEach(sub => sub.unsubscribe());
      }
  }
}

Jawaban #2

Coba di konstruktor komponen Radio sebagai gantinya

constructor(@Optional() radioGroup: RadioGroup, public cd: ChangeDetectorRef)

kode ini

constructor(@Inject(forwardRef(() => RadioGroup)) radioGroup: RadioGroup, public cd: ChangeDetectorRef)

Artikel terkait