【Angular アンチパターン】子コンポーネントのメソッドを親コンポーネントから直接呼び出すのは適切...?

コンポーネントどうしが疎結合な状態を目指す設計を採用している限り適切ではなさそう、というお話。

親コンポーネント側から子コンポーネントのメソッドを呼び出す

親コンポーネント(parent)と子コンポーネント(child)を用意します。

parent.component.html
<div id="body">
  <h1>{{message}}</h1>
  <div>
    <button (click)="changeMessage()">change message</button>
  </div>
  <app-child></app-child>
</div>

↑ここで子コンポーネントを書いとかないと

Cannot read property 'changeMessage' of undefined

と怒られるけどchildComponentがundefinedなんだから当たり前の話。 <app-child></app-child>をつければ解決する。 解決はするけど......ぜひ最後まで読んでいってください。


parent.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  styleUrls: ['./parent.component.css'],
  templateUrl: './parent.component.html'
})

export class ParentComponent implements OnInit {
  message:string;
  @ViewChild(ChildComponent,{static: false})
  private childComponent:ChildComponent;

  constructor() { }

  ngOnInit() {
    this.message = 'Loading...';
  }
  
  changeMessage(){
    this.message= this.childComponent.changeMessage();
  }
}

Angular8は@ViewChildの引数が二つになることに注意が必要。 {static: false}を第二引数にすればいままでどおりに使用できます。

(参考:https://angular.jp/api/core/ViewChild)


child.conponent.ts
import { Component, OnInit, Input, Output,EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {

  @Input() content:string[];
  @Output() action = new EventEmitter<MouseEvent>();

  constructor() { }

  ngOnInit() { }

  changeMessage(){
    return "Change!!!";
  }
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ParentComponent } from './parent/parent.component';
import { ChildComponent } from './child/child.component';

@NgModule({
  declarations: [
    AppComponent,
    ParentComponent,
    ChildComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [ParentComponent]
})
export class AppModule { }

実行してみると…

IMB Svgo2t

動いちゃう。書くことは可能みたいです。

コンポーネントどうしが依存してしまう

この方法だと親コンポーネントは子コンポーネントのメソッドがいなくなった途端に動作しなくなってしまいます。

他の言語でも子クラスが親クラスメソッドを呼ぶことはあっても、親クラスが子クラスのメソッドを知っている設計はなかなかやらないですよね。

Angularはコンポーネントどうしは疎結合な関係になるように作るのが定石のようです。 親コンポーネント側から子コンポーネントの一部のメソッドを呼びたくなった際は、そのメソッドはサービスのみ切り分けて独立させるのが便利かつ疎結合な設計を保てる方法とのこと。

そもそも書いてる最中にあちこち移動してよくわからなくなったのであとで保守する人のためにも疎結合を徹底した方がみんな幸せだなあと感じました。