简体   繁体   中英

Angular 2: how to change @Input property value during ngOnInit/ngOnChanges

Problem

I am creating a component with 2 @Input() properties, and they happen to depend on one another. Here's an example (also you can see this plunk ):

@Component({
  selector: 'my-select',
  template: `
    <ul>
      <li *ngFor="let item of items">
        {{ item }}
        <span *ngIf="item === default">(default)</span>
        <button *ngIf="item !== default" (click)="makeDefault(item)">Make default</button>
      </li>
    </ul>
  `,
}) export class MyInput implements OnInit {
  @Input() public items: string[] = [];

  @Input() public defaultValue: string;
  @Output() public defaultValueChange = new EventEmitter<string>();

  public makeDefault(item: string): void {
    this.defaultValue = item;
    this.defaultValueChange.emit(this.defaultValue);
  }

  public ngOnInit(): void {
    if (!this.defaultValue) {
      // choose the first item, since there always must be a default
      this.makeDefault(this.items[0]);
    }
  }
}

The actual problem is that I'm changing an @Input() property value during an ongoing change-detection cycle, which is not very awesome (and result in an error thrown by Angular). This is happening assuming that

<my-select [(default)]="defaultItem" ...></my-select>

syntax is used, which is exactly the expected usage pattern.

I can fix this by surrounding makeDefault call with setTimeout , like this:

  public ngOnInit(): void {
    if (!this.defaultValue) {
      setTimeout(() => {
        this.makeDefault(this.items[0]);
      }, 0);
    }
  }

but this is pretty ugly, IMHO.

What is the proper way to handle such interaction patterns?

To set the default value, you must use:

[defaultValue]="default"

Not:

[(defaultValue)]="default"

Check out the updated plnkr . I wired up the defaultValueChange event too.

Update from comment:

Also, instead of initializing 'default' in the my-app template, using a default value declared in the App class overcomes this issue too:

<my-select [(default)]="defaultItem" ...></my-select>

export class App {
  defaultItem = 'default';
  items = ['item 1', 'item 2', 'item 3'];
  constructor() {
    this.name = 'Angular2'
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM