Как выполнить двустороннюю привязку моего собственного RxJS В соответствии с [(ngModel)]?
Есть ли короткий и простой способ передать RxJS Subject
или BehaviorSubject
в директиву Angular 2 для двусторонней привязки? Длинный способ сделать это будет следующим:
@Component({
template: `
<input type="text" [ngModel]="subject | async" (ngModelChange)="subject.next($event)" />
`
})
Я хотел бы сделать что-то вроде этого:
@Component({
template: `
<input type="text" [(ngModel)]="subject" />
`
})
Я считаю, что труба async
является только односторонней, так что этого недостаточно. Предлагает ли Angular 2 короткий и простой способ сделать это? Angular 2 также использует RxJS, поэтому я ожидал, что там будет какая-то совместимая совместимость.
Могу ли я создать новую директиву ngModel
, чтобы сделать это возможным?
Ответы
Ответ 1
Ближайшим, о котором я могу думать, является использование FormControl:
import { FormControl } from '@angular/forms';
@Component({
template: '<input [formControl]="control">'
})
class MyComponent {
control = new FormControl('');
constructor(){
this.control.valueChanges.subscribe(()=> console.log('tada'))
}
}
Ответ 2
Я начал изучать что-то подобное, чтобы интегрировать элементы управления формы с моей библиотекой ng-app-state. Если вы тип, которому нравится делать очень общий, библиотечный код, тогда читайте дальше. Но будьте осторожны, это долго! В конце вы сможете использовать это в своих шаблонах:
<input [subjectModel]="subject">
Я сделал доказательство концепции для первой половины этого ответа, а вторая половина, я считаю, верна, но следует предупредить, что ни один фактический код, написанный в этом ответе, не проверен. Извините, но это лучшее, что я могу предложить прямо сейчас.:)
Вы можете написать свою собственную директиву subjectModel
, чтобы связать объект с компонентом формы. Ниже приводятся основные части, минус такие вещи, как очистка. Он опирается на ControlValueAccessor
интерфейс, поэтому Angular содержит необходимые адаптеры, чтобы связать это со всеми стандартными элементами HTML-формы, и, он будет работать с любыми настраиваемыми элементами управления формы, которые вы найдете в дикой природе, если они используют ControlValueAccessor
(что рекомендуется).
@Directive({ selector: '[subjectModel]' })
export class SubjectModelDirective {
private valueAccesor: ControlValueAccessor;
constructor(
@Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[],
) {
this.valueAccessor = valueAccessors[0]; // <- this can be fancier
}
@Input() set subjectModel(subject: Subject) {
// <-- cleanup here if this was already set before
subject.subscribe((newValue) => {
// <-- skip if this is already the value
this.valueAccessor.writeValue(newValue);
});
this.valueAccessor.registerOnChange((newValue) => {
subject.next(newValue);
});
}
}
Мы могли бы остановиться здесь, и вы сможете написать это в своих шаблонах:
<input [subjectModel]="subject" [ngDefaultControl]>
Это дополнительное [ngDefaultControl]
существует, чтобы вручную вызвать Angular для предоставления необходимой ControlValueAccessor
нашей директивы. Другие виды входов (например, переключатели и кнопки выбора) нуждаются в другой дополнительной директиве. Это связано с тем, что Angular автоматически не прикрепляет атрибуты значений к каждому компоненту формы, только те, которые также имеют ngModel
, formControl
или formControlName
.
Если вы хотите пройти лишнюю милю, чтобы устранить необходимость в этих дополнительных директивах, вам придется по существу скопировать их в свой код, но изменить их селекторы для активации для вашего нового subjectModel
. Это полностью непроверенная часть, но я считаю, что вы могли бы это сделать:
// This is copy-paste-tweaked from
// https://angular.io/api/forms/DefaultValueAccessor
@Directive({
selector: 'input:not([type=checkbox])[subjectModel],textarea[subjectModel]',
host: {
'(input)': '_handleInput($event.target.value)',
'(blur)': 'onTouched()',
'(compositionstart)': '_compositionStart()',
'(compositionend)': '_compositionEnd($event.target.value)'
},
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultSubjectModelValueAccessor extends DefaultValueAccessor {}
Кредит за мое понимание этого относится к ngrx-forms, который использует эту технику.
Ответ 3
"Если гора не придет к Мухаммеду, то Мухаммед должен идти к горе"
Давайте подойдем к этому со стороны RxJS, а не со стороны NgModule.
Это решение ограничивает нас в использовании только BehaviorSubject
но я думаю, что это справедливая сделка для такого простого решения.
Вставьте этот кусок кода в ваши polyfills.ts. Это позволяет вам связать .value
BehaviorSubject
с ngModule
import { BehaviorSubject } from 'rxjs';
Object.defineProperty(BehaviorSubject.prototype, 'value', {
set: function(v) {
return this.next(v);
}
});
И просто используйте это так.
<ng5-slider [(value)]="fooBehaviorSubject.value" ...
PS: Я как раз собирался открыть запрос об этом в репозитории RxJS на github, но оказалось, что кто-то только что сделал такой же запрос всего 3 часа назад. Если вы хотите, чтобы эта функция была реализована, пожалуйста, подтвердите запрос.