LoginSignup
4
0

More than 3 years have passed since last update.

複数コンポーネント間で双方向バインディングするのはプリミティブではなくオブジェクトにしたほうがいいらしい

Last updated at Posted at 2019-12-25

問題

angular バージョン

親子関係にあるchild-conponentとparent-componentがあり、さらにparent-componentと親子関係にあるgrand-parentがあるとします。

  • grand-parent-component
  • parent-component
  • child-component

この3コンポーネント間でプリミティブ型のデータを双方向バインディングする場合、childコンポーネントのデータの変化がgrand-parentコンポーネントまで伝わらないという現象に遭遇しました。

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

@Component({
  selector: 'app-child',
  template: `<input type="checkbox" [(ngModel)]="flag"> <p>child: {{flag}}</p>`,
})
export class ChildComponent {
  @Input() flag: boolean;
  @Output() flagChange: EventEmitter<boolean> = new EventEmitter();
}
parent-component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `<app-child [(flag)]="flag"></app-child> <p>parent: {{flag}}</p>`,
})
export class ParentComponent {
  @Input() flag: boolean;
  @Output() flagChange: EventEmitter<boolean> = new EventEmitter();
}
grand-parent-component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-grand-parent',
  template: `<app-parent [(flag)]="flag"></app-parent> <p>grandParent: {{flag}}</p>`,
})
export class GrandParentComponent {
  flag: boolean = false;
}

ezgif.com-video-to-gif.gif

次のようにflagChangeをemitする場合、parentコンポーネントまでは伝わります。

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

@Component({
  selector: 'app-child',
  template: `<input type="checkbox" [(ngModel)]="flag" (click)="onFlagChange()"> <p>child: {{flag}}</p>`,
})
export class ChildComponent {
  @Input() flag: boolean;
  @Output() flagChange: EventEmitter<boolean> = new EventEmitter();

  onFlagChange() {
    this.flag = !this.flag;
    this.flagChange.emit(this.flag);
  }
}
parent-component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `<app-child [(flag)]="flag"></app-child> <p>parent: {{flag}}</p>`,
})
export class ParentComponent {
  @Input() flag: boolean;
  @Output() flagChange: EventEmitter<boolean> = new EventEmitter();
}
grand-parent-component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-grand-parent',
  template: `<app-parent [(flag)]="flag"></app-parent> <p>grandParent: {{flag}}</p>`,
})
export class GrandParentComponent {
  flag: boolean = false;
}

ezgif.com-video-to-gif (1).gif

しかしこの方法ではgrand-parent-componentまではデータが伝わりません。

解決策

プリミティブ型ではなくオブジェクトのプロパティとしてプリミティブ型を渡してあげる。

つまりこのようにする。

child-component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Flag } from 'src/app/flag';

@Component({
  selector: 'app-child',
  template: `<input type="checkbox" [(ngModel)]="flag.isSelected"> <p>child: {{flag.isSelected}}</p>`,
})
export class ChildComponent {
  @Input() flag: Flag;
  @Output() flagChange: EventEmitter<boolean> = new EventEmitter();
}
parent-component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Flag } from 'src/app/flag';

@Component({
  selector: 'app-parent',
  template: `<app-child [(flag)]="flag"></app-child> <p>parent: {{flag.isSelected}}</p>`,
})
export class ParentComponent {
  @Input() flag: Flag;
  @Output() flagChange: EventEmitter<boolean> = new EventEmitter();
}
grand-parent-component.ts
import { Component } from '@angular/core';
import { Flag } from 'src/app/flag';

@Component({
  selector: 'app-grand-parent',
  template: `<app-parent [(flag)]="flag"></app-parent> <p>grandParent: {{flag.isSelected}}</p>`,
})
export class GrandParentComponent {
  flag: Flag = { isSelected: false };
}

ezgif.com-video-to-gif (2).gif

なぜこのような現象が起こるのか

こちらの記事ではangularjsですが同じような問題を解説しており、javascriptの評価戦略(ある式をどのような手順で評価するかの規則)が関係していると述べております。

javascriptの評価戦略ではpremitive型の変数は参照元から渡されるのではなく、同じ変数を再び作ってしまいます。オブジェクトのみが参照元から渡されます。

こちらの記事でもngModelはprimitive型のデータではなくオブジェクトにバインドすることを推奨しております。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0