是否有一种简短的方法将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
式指令来实现这一目标吗?
我能想到的最接近的是使用FormControl:
import { FormControl } from '@angular/forms';
@Component({
template: '<input [formControl]="control">'
})
class MyComponent {
control = new FormControl('');
constructor(){
this.control.valueChanges.subscribe(()=> console.log('tada'))
}
}
我已经开始研究这样的东西,将表单控件与我的库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不会自动将值访问器附加到每个表单组件,只有那些也具有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。
“如果山不会来到穆罕默德,那么穆罕默德必须去山上”
让我们从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 repo上打开一个关于此的请求,但事实证明有人刚刚制作了the exact same request just 3 hours ago.如果您希望实现此功能,请认可该请求。