Angular開発実践(四):コンポーネント間のインタラクション

11381 ワード

Angularアプリケーション開発では,コンポーネントは随所に見られるといえる.この記事では、2つ以上のコンポーネントをインタラクティブにする方法として、いくつかの一般的なコンポーネント通信シーンについて説明します.
データの伝達方向によって,親コンポーネントから子コンポーネントへの伝達,子コンポーネントから親コンポーネントへの伝達,およびサービスによる伝達の3つのインタラクション方法に分けられる.

親コンポーネントが子コンポーネントに渡される


サブアセンブリは@Inputアクセラレータによって入力属性を定義し、親アセンブリがサブアセンブリを参照するときにこれらの入力属性によってサブアセンブリにデータを渡し、サブアセンブリはsetterまたはngOnChanges()によって入力属性値の変化を聞くことができる.
まず、 DemoChildComponentおよび DemoParentComponentの2つの構成要素を定義.
サブアセンブリ:
@Component({
  selector: 'demo-child',
  template: `
    

{{paramOne}}

{{paramTwo}}

` }) export class DemoChildComponent { @Input() paramOne: any; // 1 @Input() paramTwo: any; // 2 }

サブアセンブリは、@Input()によって入力属性paramOneおよびparamTwoを定義する(属性値は任意のデータ型であってもよい).
親コンポーネント:
@Component({
  selector: 'demo-parent',
  template: `
    
  `
})
export class DemoParentComponent {
    paramOneVal: any = '   paramOne   ';
    paramTwoVal: any = '   paramTwo   ';
}

親コンポーネントは、そのテンプレートにおいてセレクタdemo-childを介して DemoChildComponentを参照し、サブコンポーネントの2つの入力属性paramOneおよびparamTwoを介してサブコンポーネントにデータを渡し、最後にサブコンポーネントのテンプレートに paramOne および paramTwo の2行のテキストを表示する.

入力属性値の変化をsetterで傍受する


実際の応用では、ある入力属性値が変化したときに対応する操作を行う必要があります.この場合、入力属性のsetterを使用して入力属性値の変化を聞く必要があります. DemoChildComponentを次のように改造します.
@Component({
  selector: 'demo-child',
  template: `
    

{{paramOneVal}}

{{paramTwo}}

` }) export class DemoChildComponent { private paramOneVal: any; @Input() set paramOne (val: any) { // 1 this.paramOneVal = val; // dosomething }; get paramOne () { return this.paramOneVal; }; @Input() paramTwo: any; // 2 }

上記のコードでは、ブロックされた値paramOneval属性のsetterによって内部プライベート属性paramOneValに付与し、親コンポーネントがサブコンポーネントにデータを渡す効果を達成することができる.もちろん、最も重要なのは、setterの中でもっと他の操作をすることができて、プログラムの柔軟性がもっと強くなります.

入力属性値の変化をngOnChanges()で聞く

setter の方法は単一の属性値の変化を監視するしかなく,複数のインタラクティブ入力属性を監視する必要がある場合,この方法は力不足に見える.一方、複数の入力属性値の変化を同時に監視することは、OnChangesライフサイクルフックインタフェースのngOnChanges()メソッド(コンポーネントが@Inputデザイナによって明示的に指定された変数の値変化によって呼び出される)を使用することによって実現される. DemoChildComponentngOnChangesが追加されました.
@Component({
  selector: 'demo-child',
  template: `
    

{{paramOneVal}}

{{paramTwo}}

` }) export class DemoChildComponent implements OnChanges { private paramOneVal: any; @Input() set paramOne (val: any) { // 1 this.paramOneVal = val; // dosomething }; get paramOne () { return this.paramOneVal; }; @Input() paramTwo: any; // 2 ngOnChanges(changes: {[propKey: string]: SimpleChange}) { for (let propName in changes) { // changes let changedProp = changes[propName]; // propName let to = JSON.stringify(changedProp.currentValue); // if (changedProp.isFirstChange()) { // console.log(`Initial value of ${propName} set to ${to}`); } else { let from = JSON.stringify(changedProp.previousValue); // console.log(`${propName} changed from ${from} to ${to}`); } } } }

新しいngOnChangesメソッドで受信されたパラメータchangesは、入力属性名をキーとし、値がSimpleChangeのオブジェクトであり、SimpleChangeオブジェクトには、現在の入力属性が最初に変化したかどうか、以前の値、現在の値などの属性が含まれている.したがって、ngOnChangesメソッドでは、changesオブジェクトを巡回することによって、複数の入力属性値を監視し、対応する動作を行うことができる.

親コンポーネントインスタンスの取得


前述したのは、親コンポーネントが入力プロパティを介してサブコンポーネントにデータを渡すことができるように、@Input によって入力プロパティを定義することです.
もちろん、親コンポーネントのインスタンスを取得し、親コンポーネントのプロパティまたはメソッドを呼び出して必要なデータを取得するよりアクティブな方法が考えられます.各コンポーネントのインスタンスが注入器のコンテナに追加されることを考慮すると、注入に依存して親コンポーネントの例を見つけることができます. は、 ( @ViewChildまたは@ViewChildrenを直接介して取得される)よりも面倒です.
子コンポーネントで親コンポーネントのインスタンスを取得するには、次の2つのケースがあります.
  • 既知の親コンポーネントのタイプこの場合、構造関数にDemoParentComponentを直接注入することによって、既知のタイプの親コンポーネント参照を取得することができる.コード例は、
    @Component({
      selector: 'demo-child',
      template: `
        

    {{paramOne}}

    {{paramTwo}}

    ` }) export class DemoChildComponent { paramOne: any; paramTwo: any; constructor(public demoParent: DemoParentComponent) { // demoParent this.paramOne = demoParent.paramOneVal; this.paramTwo = demoParent.paramTwoVal; } }
  • である.
  • 未知の親コンポーネントのタイプ1つのコンポーネントは、複数のコンポーネントのサブコンポーネントであり、親コンポーネントのタイプを直接知ることができない場合があります.Angularでは、 — (Class-Interface)によって検索できます.つまり、親コンポーネントは、と同じ名前の別名を提供することによって検索を支援します.まずDemoParent抽象クラスを作成します.これはparamOneValparamTwoValの属性のみを宣言し、実装されていません(割り当てられています).サンプルコードは、
    export abstract class DemoParent {
        paramOneVal: any;
        paramTwoVal: any;
    }
    で、 DemoParentComponentprovidersメタデータに別名Providerを定義し、useExistingを使用して親コンポーネントDemoParentComponentのインスタンスを注入します.コードの例は、次のとおりです.
    @Component({
      selector: 'demo-parent',
      template: `
        
      `,
      providers: [{provider: DemoParent, useExisting: DemoParentComponent}]
    })
    export class DemoParentComponent implements DemoParent {
        paramOneVal: any = '   paramOne   ';
        paramTwoVal: any = '   paramTwo   ';
    }
    で、親コンポーネントの例は、子コンポーネントでDemoParentという識別によって見つけることができます.例コードは、
    @Component({
      selector: 'demo-child',
      template: `
        

    {{paramOne}}

    {{paramTwo}}

    ` }) export class DemoChildComponent { paramOne: any; paramTwo: any; constructor(public demoParent: DemoParent) { // demoParent this.paramOne = demoParent.paramOneVal; this.paramTwo = demoParent.paramTwoVal; } }
  • です.

    子コンポーネントが親コンポーネントに渡される


    2つの構成要素は、それぞれ DemoChildComponentおよび DemoParentComponentである.
    サブアセンブリ:
    @Component({
      selector: 'demo-child',
      template: `
        

    DemoChildComponent

    ` }) export class DemoChildComponent implements OnInit { readyInfo: string = ' DemoChildComponent !'; @Output() ready: EventEmitter = new EventEmitter(); // ngOnInit() { this.ready.emit(this.readyInfo); } }

    親コンポーネント:
    @Component({
      selector: 'demo-parent',
      template: `
        
        

    readyInfo: {{demoChild.readyInfo}}

    readyInfo: {{demoChildComponent.readyInfo}}

    ` }) export class DemoParentComponent implements AfterViewInit { // @ViewChild('demoChild') demoChildComponent: DemoChildComponent; // @ViewChild(DemoChildComponent) demoChildComponent: DemoChildComponent; // ngAfterViewInit() { console.log(this.demoChildComponent.readyInfo); // : DemoChildComponent ! } onReady(evt: any) { console.log(evt); // : DemoChildComponent ! } }

    親コンポーネントが子コンポーネントのイベントをリスニングする


    サブアセンブリは、イベントが発生したときに、サブアセンブリがそのプロパティemits(上へ弾き出す)イベントを利用するEventEmitterプロパティを露出します.親コンポーネントはこのイベントプロパティにバインドされ、イベントが発生したときに応答します.
    上で定義した子コンポーネントと親コンポーネントは、次のように表示されます.
    サブアセンブリは、@Output()によって出力属性readyを定義し、その後、ngOnInitにおいてready属性のemitsイベント(上向き弾射)を利用する.
    親コンポーネントは、そのテンプレートにおいてセレクタdemo-childによって DemoChildComponentを参照し、サブコンポーネントのイベント($event)に応答してデータを印刷するためのイベントプロセッサ(onReady())をバインドし、フレームワーク(Angular)はイベントパラメータ($eventで表される)をイベント処理方法に渡す.

    親コンポーネントと子コンポーネントは、ローカル変数(テンプレート変数)でインタラクティブです。


    親コンポーネントは、データバインドを使用してサブコンポーネントのプロパティを読み取る方法や、サブコンポーネントを呼び出す方法は使用できません.ただし、親コンポーネントテンプレートで、サブコンポーネントを表すローカル変数を新規作成し、この変数を使用してサブコンポーネントのプロパティを読み取り、サブコンポーネントを呼び出す方法を使用することができます.
    上で定義した子コンポーネントと親コンポーネントは、次のように表示されます.
    親コンポーネントは、テンプレートdemo-childのラベルにdemoChildのローカル変数を定義し、テンプレートでサブコンポーネントのプロパティを取得します.

    readyInfo: {{demoChild.readyInfo}}


    親コンポーネント呼び出し@ViewChild()


    ローカル変数メソッドは簡単で便利な方法です.ただし、親コンポーネント-サブコンポーネントの接続はすべて親コンポーネントのテンプレートで行う必要があるため、限界もあります.親コンポーネント自体のコードは、サブコンポーネントにアクセスできません.
    親コンポーネントのクラスがサブコンポーネントのプロパティ値を読み込むか、サブコンポーネントを呼び出す方法が必要な場合は、ローカル変数メソッドは使用できません.
    親コンポーネントクラスがこのアクセスを必要とする場合、子コンポーネントをViewChildとして親コンポーネントに注入できます.
    上で定義した子コンポーネントと親コンポーネントは、次のように表示されます.
    親コンポーネントは、コンポーネントクラスで@ViewChild()を使用して子コンポーネントのインスタンスを取得し、テンプレートまたはコンポーネントクラスでそのインスタンスを使用して子コンポーネントのプロパティを取得できます.

    readyInfo: {{demoChildComponent.readyInfo}}

    ngAfterViewInit() {
        console.log(this.demoChildComponent.readyInfo); //     :   DemoChildComponent     !
    }

    サービスによる転送


    Angularのサービスは、モジュール注入またはコンポーネント注入(いずれもproviders注入)することができる.
    モジュールに注入されたサービスは、Angularアプリケーション全体にわたってアクセス可能である(不活性にロードされたモジュールを除く).
    コンポーネントに注入されたサービスには、コンポーネントとそのサブコンポーネントしかアクセスできません.このコンポーネントのサブツリー以外のコンポーネントは、サービスにアクセスしたり通信したりできません.
    次の例では、コンポーネントに注入されたサービスを使用して、親子コンポーネント間のデータ転送を行います.
    通信サービス:
    @Injectable()
    export class CallService {
        info: string = '  CallService info';
    }

    親コンポーネント:
    @Component({
      selector: 'demo-parent',
      template: `
        
        
        

    {{callService.info}}

    `, providers: [CallService] }) export class DemoParentComponent { constructor(public callService: CallService) { console.log(callService.info); // : CallService info } changeInfo() { this.callService.info = ' CallService info'; } }

    サブアセンブリ:
    @Component({
      selector: 'demo-child',
      template: `
        
      `
    })
    export class DemoChildComponent {
        constructor(public callService: CallService) {
            console.log(callService.info); //     :  CallService info
        }
        
        changeInfo() {
            this.callService.info = '         CallService info';
        }
    }

    上記のコードでは、info属性を定義したCallServiceサービスを定義し、この属性の値を親子コンポーネントで変更することで、親子コンポーネントがデータを相互に伝達する目的を達成します.
    次に、DelmoParentComponentのprovidersメタデータ配列によってCallServiceサービスのインスタンスが提供され、構造関数によって親子コンポーネントにそれぞれ注入される.
    このとき、CallServiceサービスのinfo属性値は、 info または info によって親コンポーネントまたは子コンポーネントで変更され、変更後に対応するinfo属性値がページに表示される.