Подтвердить что ты не робот

Как выполнить двустороннюю привязку моего собственного 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, чтобы сделать это возможным?

4b9b3361

Ответ 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 часа назад. Если вы хотите, чтобы эта функция была реализована, пожалуйста, подтвердите запрос.