如何双向绑定我自己的RxJS主题为[(ngModel)]?

mhe*_*ens 31 rxjs angular-ngmodel rxjs5 angular

是否有简短的方法来传递RxJS SubjectBehaviorSubjectAngular 2指令以进行双向绑定?其中很长的路要做如下:

@Component({
    template: `
        <input type="text" [ngModel]="subject | async" (ngModelChange)="subject.next($event)" />
    `
})
Run Code Online (Sandbox Code Playgroud)

我希望能够做到这样的事情:

@Component({
    template: `
        <input type="text" [(ngModel)]="subject" />
    `
})
Run Code Online (Sandbox Code Playgroud)

我相信async管道只是单向的,所以这还不够.Angular 2是否提供了一种简单易行的方法?Angular 2也使用RxJS,所以我预计会有一些固有的兼容性.

我是否可以创建一个新ngModel的指令来实现这一目标?

act*_*cay 10

正如您在问题中所说,这是一个简单的解决方案。我认为没有什么比你已经提供的更简单了。

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


Die*_*ein 10

一个可能的解决方案是BehaviorSubject 的子类:

class ModelSubject<T> extends BehaviorSubject<T> {

    constructor(initialValue: T) {
        super(initialValue);
    }

    set model(value: T) {
        this.next(value);
    }

    get model(): T {
        return this.value;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

组件类别:

name = new ModelSubject<string>('');
Run Code Online (Sandbox Code Playgroud)

组件模板:

<input [(ngModel)]="name.model">
Run Code Online (Sandbox Code Playgroud)


Eri*_*ton 5

我已经开始研究类似的方法来将表单控件与我的库ng-app-state集成。如果您是那种喜欢编写非常通用的、类似库的代码的人,那么请继续阅读。但请注意,这很长!最后你应该能够在你的模板中使用它:

<input [subjectModel]="subject">
Run Code Online (Sandbox Code Playgroud)

我已经对该答案的前半部分进行了概念验证,我认为后半部分是正确的,但请注意,该答案中编写的实际代码均未经过测试。抱歉,但这是我现在能提供的最好的了。:)

您可以编写自己的指令,称为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);
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以到此为止,您将能够在模板中编写以下内容:

<input [subjectModel]="subject" [ngDefaultControl]>
Run Code Online (Sandbox Code Playgroud)

该额外的[ngDefaultControl]存在是为了手动使 Angular 为我们的指令提供所需的内容ControlValueAccessor。其他类型的输入(例如单选按钮和选择)将需要不同的额外指令。这是因为 Angular 不会自动将值访问器附加到每个表单组件,只有那些也具有ngModelformControl或 的组件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 {}
Run Code Online (Sandbox Code Playgroud)

我对这一点的理解归功于ngrx-forms,它采用了这种技术。


Győ*_*dor 5

“如果山不肯来穆罕默德,那么穆罕默德就必须去山”

让我们从 RxJS 端而不是 NgModel 端来处理这个问题。

这个解决方案限制我们只能使用BehaviorSubject's 但我认为对于拥有如此简单的解决方案来说这是一个公平的交易。

将这段代码放入您的 polyfills.ts 中。这使您能够将.valuea绑定BehaviorSubjectngModel

import { BehaviorSubject } from 'rxjs';

Object.defineProperty(BehaviorSubject.prototype, 'value', {
    set: function(v) {
        return this.next(v);
    }
});
Run Code Online (Sandbox Code Playgroud)

就像这样使用它。

<ng5-slider [(value)]="fooBehaviorSubject.value" ...
Run Code Online (Sandbox Code Playgroud)


小智 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'))
    }
}
Run Code Online (Sandbox Code Playgroud)