Как использовать двухстороннюю привязку данных между компонентами в angular 2?

Сначала я создал класс User:

export class User {
  name: string;
  email: string;
}

Затем у меня есть мой CoreComponent, который использует FormInputComponent, а также создает public user из класса User:

import {Component} from 'angular2/core';
import {FormInputComponent} from '../form-controls/form-input/form-input.component';
import {User} from '../core/user';

@Component({
  selector: 'core-app',
  templateUrl: './app/assets/scripts/modules/core/core.component.html',
  styleUrls: ['./app/assets/scripts/modules/core/core.component.css'],
  directives: [FormInputComponent]
})

export class CoreComponent {
  public user: User = {
    name: '',
    email: ''
  }
}

Затем я создал компонент ввода, который является повторно используемым входным компонентом, который будет принимать значение модели в качестве ввода, и когда изменения будут производиться, экспортируйте новое значение, чтобы CoreComponent мог обновить модель с помощью нового значение:

import {Component, Input, Output, EventEmitter, DoCheck} from 'angular2/core';

@Component({
  selector: 'form-input',
  templateUrl: './app/assets/scripts/modules/form-controls/form-input/form-input.component.html',
  styleUrls: ['./app/assets/scripts/modules/form-controls/form-input/form-input.component.css'],
  inputs: [
    'model',
    'type',
    'alt',
    'placeholder',
    'name',
    'label'
  ]
})

export class FormInputComponent implements DoCheck {
  @Input() model: string;
  @Output() modelExport: EventEmitter = new EventEmitter();

  ngDoCheck() {
    this.modelExport.next(this.model);
  }
}

Шаблон CoreComponent использует два FormInputComponent и передает user.name и user.email в качестве входа для них:

<form-input [model]="user.name" type="text" name="test" placeholder="This is a test" alt="A test input" label="Name"></form-input>
<form-input [model]="user.email" type="email" name="test" placeholder="This is a test" alt="A test input" label="Email"></form-input>
<pre>{{user.name}}</pre>

Шаблон FormInputComponent:

<div>
  <label attr.for="{{name}}">{{label}}</label>
  <input [(ngModel)]="model" type="{{type}}" placeholder="{{placeholder}}" alt="{{alt}}" id="{{name}}">
</div>
<pre>{{model}}</pre>

Теперь проблема в том, что я могу видеть только изменения из элемента pre, который находится внутри шаблона FormInputComponent, но элемент parent, CoreComponent pre не изменяется.

Я посмотрел на этот вопрос, который находится в шаге от того, чего я хочу достичь, но не совсем так, потому что, используя сервис для просто возвращающего значения, иерархия кажется overkill и немного беспорядочно, если на одной странице есть несколько FormInputComponent.

Итак, мой вопрос прост, как передать модель в FormInputComponent и позволить ей возвращать новое значение всякий раз, когда значение изменяется, так что public user в CoreComponent автоматически изменяется?

Ответы

Ответ 1

Чтобы иметь возможность использовать двустороннюю привязку коротко при использовании вашего компонента, вам нужно прочитать свое свойство вывода в modelChange:

export class FormInputComponent implements DoCheck {
  @Input() model: string;
  @Output() modelChange: EventEmitter = new EventEmitter();

  ngDoCheck() {
    this.modelChange.next(this.model);
  }
}

И используйте его так:

<form-input [(model)]="user.name" type="text" name="test" placeholder="This is a test" alt="A test input" label="Name"></form-input>
<form-input [(model)]="user.email" type="email" name="test" placeholder="This is a test" alt="A test input" label="Email"></form-input>
<pre>{{user.name}}</pre>

Ответ 2

В качестве дополнения: Использование ngDoCheck для изменения изменений модели будет ДРАКЦИОННО влиять на производительность по мере того, как новое значение будет испускаться в каждом контрольном цикле, независимо от того, было ли оно изменено или нет. И это действительно часто! (Попробуйте console.log!)

Итак, что мне нравится делать, чтобы получить сопоставимое удобство, но без побочных эффектов, это примерно так:

private currentSelectedItem: MachineItem;
@Output() selectedItemChange: EventEmitter<MachineItem> = new EventEmitter<MachineItem>();

@Input() set selectedItem(machineItem: MachineItem) {
    this.currentSelectedItem = machineItem;
    this.selectedItemChange.emit(machineItem); 
}

get selectedItem(): MachineItem {
    return this.currentSelectedItem; 
}

И используйте его как

<admin-item-list [(selectedItem)]="selectedItem"></admin-item-list>

Вы также можете испустить новое значение, где оно фактически изменено. Но мне очень удобно делать эту злободневность в методе сеттера и не нужно беспокоиться, когда я привязываю его прямо к себе.

Ответ 3

Это слишком долго, чтобы добавить комментарий к ответу Тьерри...

Используйте emit() вместо next() (который устарел) и вызывайте emit(newValue) только при изменении значения model. Скорее всего, вы хотите что-то подобное в шаблоне FormInputComponent:

<input [ngModel]="model" (ngModelChange)="onChanges($event)">

Затем в вашей логике FormInputComponent:

onChanges(newValue) {
  this.model = newValue;
  this.changeModel.emit(newValue);
}