Angular開発実践(四):コンポーネント間のインタラクション
11381 ワード
Angularアプリケーション開発では,コンポーネントは随所に見られるといえる.この記事では、2つ以上のコンポーネントをインタラクティブにする方法として、いくつかの一般的なコンポーネント通信シーンについて説明します.
データの伝達方向によって,親コンポーネントから子コンポーネントへの伝達,子コンポーネントから親コンポーネントへの伝達,およびサービスによる伝達の3つのインタラクション方法に分けられる.
サブアセンブリは@Inputアクセラレータによって入力属性を定義し、親アセンブリがサブアセンブリを参照するときにこれらの入力属性によってサブアセンブリにデータを渡し、サブアセンブリは
まず、
サブアセンブリ:
サブアセンブリは、
親コンポーネント:
親コンポーネントは、そのテンプレートにおいてセレクタ
実際の応用では、ある入力属性値が変化したときに対応する操作を行う必要があります.この場合、入力属性のsetterを使用して入力属性値の変化を聞く必要があります.
上記のコードでは、ブロックされた値
新しい
前述したのは、親コンポーネントが入力プロパティを介してサブコンポーネントにデータを渡すことができるように、
もちろん、親コンポーネントのインスタンスを取得し、親コンポーネントのプロパティまたはメソッドを呼び出して必要なデータを取得するよりアクティブな方法が考えられます.各コンポーネントのインスタンスが注入器のコンテナに追加されることを考慮すると、注入に依存して親コンポーネントの例を見つけることができます.
子コンポーネントで親コンポーネントのインスタンスを取得するには、次の2つのケースがあります.既知の親コンポーネントのタイプこの場合、構造関数にDemoParentComponentを直接注入することによって、既知のタイプの親コンポーネント参照を取得することができる.コード例は、 である.未知の親コンポーネントのタイプ1つのコンポーネントは、複数のコンポーネントのサブコンポーネントであり、親コンポーネントのタイプを直接知ることができない場合があります.Angularでは、 です.
2つの構成要素は、それぞれ
サブアセンブリ:
親コンポーネント:
サブアセンブリは、イベントが発生したときに、サブアセンブリがそのプロパティemits(上へ弾き出す)イベントを利用するEventEmitterプロパティを露出します.親コンポーネントはこのイベントプロパティにバインドされ、イベントが発生したときに応答します.
上で定義した子コンポーネントと親コンポーネントは、次のように表示されます.
サブアセンブリは、
親コンポーネントは、そのテンプレートにおいてセレクタ
親コンポーネントは、データバインドを使用してサブコンポーネントのプロパティを読み取る方法や、サブコンポーネントを呼び出す方法は使用できません.ただし、親コンポーネントテンプレートで、サブコンポーネントを表すローカル変数を新規作成し、この変数を使用してサブコンポーネントのプロパティを読み取り、サブコンポーネントを呼び出す方法を使用することができます.
上で定義した子コンポーネントと親コンポーネントは、次のように表示されます.
親コンポーネントは、テンプレート
ローカル変数メソッドは簡単で便利な方法です.ただし、親コンポーネント-サブコンポーネントの接続はすべて親コンポーネントのテンプレートで行う必要があるため、限界もあります.親コンポーネント自体のコードは、サブコンポーネントにアクセスできません.
親コンポーネントのクラスがサブコンポーネントのプロパティ値を読み込むか、サブコンポーネントを呼び出す方法が必要な場合は、ローカル変数メソッドは使用できません.
親コンポーネントクラスがこのアクセスを必要とする場合、子コンポーネントをViewChildとして親コンポーネントに注入できます.
上で定義した子コンポーネントと親コンポーネントは、次のように表示されます.
親コンポーネントは、コンポーネントクラスで
Angularのサービスは、モジュール注入またはコンポーネント注入(いずれも
モジュールに注入されたサービスは、Angularアプリケーション全体にわたってアクセス可能である(不活性にロードされたモジュールを除く).
コンポーネントに注入されたサービスには、コンポーネントとそのサブコンポーネントしかアクセスできません.このコンポーネントのサブツリー以外のコンポーネントは、サービスにアクセスしたり通信したりできません.
次の例では、コンポーネントに注入されたサービスを使用して、親子コンポーネント間のデータ転送を行います.
通信サービス:
親コンポーネント:
サブアセンブリ:
上記のコードでは、info属性を定義したCallServiceサービスを定義し、この属性の値を親子コンポーネントで変更することで、親子コンポーネントがデータを相互に伝達する目的を達成します.
次に、DelmoParentComponentの
このとき、CallServiceサービスのinfo属性値は、
データの伝達方向によって,親コンポーネントから子コンポーネントへの伝達,子コンポーネントから親コンポーネントへの伝達,およびサービスによる伝達の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
}
上記のコードでは、ブロックされた値
paramOne
をval
属性のsetterによって内部プライベート属性paramOneVal
に付与し、親コンポーネントがサブコンポーネントにデータを渡す効果を達成することができる.もちろん、最も重要なのは、setterの中でもっと他の操作をすることができて、プログラムの柔軟性がもっと強くなります.入力属性値の変化をngOnChanges()で聞く
setter
の方法は単一の属性値の変化を監視するしかなく,複数のインタラクティブ入力属性を監視する必要がある場合,この方法は力不足に見える.一方、複数の入力属性値の変化を同時に監視することは、OnChangesライフサイクルフックインタフェースのngOnChanges()メソッド(コンポーネントが@Input
デザイナによって明示的に指定された変数の値変化によって呼び出される)を使用することによって実現される. DemoChildComponent
にngOnChanges
が追加されました.@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つのケースがあります.
@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;
}
}
— (Class-Interface)
によって検索できます.つまり、親コンポーネントは、 —
と同じ名前の別名を提供することによって検索を支援します.まずDemoParent抽象クラスを作成します.これはparamOneVal
とparamTwoVal
の属性のみを宣言し、実装されていません(割り当てられています).サンプルコードは、export abstract class DemoParent {
paramOneVal: any;
paramTwoVal: any;
}
で、 DemoParentComponent
のproviders
メタデータに別名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属性値がページに表示される.