web-dev-qa-db-ja.com

自分のRxJSサブジェクトを[(ngModel)]に双方向バインドする方法は?

RxJS Subject または BehaviorSubject をan 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のようなディレクティブを作成できますか?

33
mhelvens

私は、フォームコントロールをライブラリ ng-app-state と統合するために、このようなものを検討し始めました。非常に一般的なライブラリのようなコードを作成するのが好きなタイプの場合は、次に進んでください。しかし、注意してください、これは長いです!最後に、これをテンプレートで使用できるようにする必要があります。

<input [subjectModel]="subject">

私はこの回答の前半について概念実証を行い、後半は正しいと信じていますが、この回答で書かれた実際のコードはどれもテストされていないことに注意してください。申し訳ありませんが、それは私が今提供しなければならない最高のものです。 :)

subjectModelという独自のディレクティブを記述して、サブジェクトをフォームコンポーネントに接続できます。以下は、クリーンアップなどを除いた重要な部分です。 ControlValueAccessor インターフェースに依存しているため、Angularには、これをすべての標準HTMLフォーム要素にフックするために必要なアダプターが含まれていますandControlValueAccessorを使用している限り、実際に使用されているカスタムフォームコントロールで動作します(推奨される方法です)。

@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がすべてのフォームコンポーネントに自動的に値アクセサをアタッチするのではなく、ngModelformControl、またはformControlName

これらの余分なディレクティブの必要性を排除するためにさらに1マイル進む場合は、基本的にそれらをコードにコピーする必要がありますが、新しい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 に行きます。

2
Eric Simonton

私が考えることができる最も近いものは、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
Quentin F

私はこれを試してみましたがうまくいきました

<div>
    <input
        #searchInput
        type="search"
        [ngModel]="searchTerm | async"
        (ngModelChange)="searchTerm.next(searchInput.value)"
    />
    {{ searchTerm | async }}
</div>

enter image description here

これがルールに違反しているかどうか、バグかハックかどうかはわかりませんが、私にとってはうまくいくようです。 Angularフォームと同じように構築されたitサブジェクトディレクティブが必要でした。

お役に立てれば

1
Mario Subotic

「もし山がムハンマドに来ないなら、ムハンマドは山に行かなければならない」

NgModule側ではなくRxJS側からこれに取り組みましょう。

この解決策はBehaviorSubjectのみを使用するように制限しますが、これはそのような簡単な解決策を講じることの公正な取引だと思います。

このコードをpolyfills.tsにスラップします。これにより、.valueBehaviorSubjectから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時間前に。 この機能を実装したい場合は、リクエストを承認してください。

1
Győri Sándor

あなたの質問で言ったように、これは簡単な解決策です。 (あなたがすでに提供したものより簡単なものはありません)

<input type="text" [ngModel]="subject | async" (ngModelChange)="subject.next($event)" />

0
activedecay