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

问题描述 投票:26回答:3

是否有一种简短的方法将RxJS SubjectBehaviorSubject传递给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式指令来实现这一目标吗?

angular rxjs angular-ngmodel rxjs5
3个回答
2
投票

我能想到的最接近的是使用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]存在以手动导致角度为我们的指令提供所需的ControlValueAccessor。其他类型的输入(如单选按钮和选择)需要不同的额外指令。这是因为Angular不会自动将值访问器附加到每个表单组件,只有那些也具有ngModelformControlformControlName的表单组件。

如果你想加倍努力以消除对这些额外指令的需要,你必须将它们复制到你的代码中,但修改它们的选择器以激活你的新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


0
投票

“如果山不会来到穆罕默德,那么穆罕默德必须去山上”

让我们从RxJS侧而不是NgModule侧接近这个。

这个解决方案限制我们只使用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 repo上打开一个关于此的请求,但事实证明有人刚刚制作了the exact same request just 3 hours ago.如果您希望实现此功能,请认可该请求。

© www.soinside.com 2019 - 2024. All rights reserved.