问题
Using RxJS, I want to fire an event if the user types 'a' then 'b' then 'c'.
I do not want the event fired if they enter 'a' then 'b' then 'z' then 'c'.
Here is my codepen of the work I have done so far (in TypeScript).
class App1 {
private divStream: HTMLElement;
private divResult: HTMLElement;
constructor(divStream: HTMLElement, divResult: HTMLElement) {
this.divStream = divStream;
this.divResult = divResult;
}
start() {
var filterByCharacter = (expectedCharater) => {
return (char) => { return char === expectedCharater; };
};
var values = ['a', 'b', 'b', 'c', 'b'];
var obChars = Rx.Observable.fromArray(values);
obChars.subscribe((k) => {
divStream.innerHTML += "<div style='color: blue'>" + ":: " + k + " ::" + "</div>";
},
(err) => {
divStream.innerHTML += "<div style='background-color: blue' > " + 'Error: ' + err + " </div>";
},
() => {
divStream.innerHTML += "<div style='background-color: blue'>" + ":: finished ::" + "</div>";
}
);
function log(text: string) {
divResult.innerHTML += "<div style='color: green'>" + text + "</div>";
}
var obA: Rx.Observable<string> = obChars.filter(filterByCharacter('a'));
var obB: Rx.Observable<string> = obChars.filter(filterByCharacter('b'));
var obC: Rx.Observable<string> = obChars.filter(filterByCharacter('c'));
let aSteps: Rx.Observable<any>[] = [];
aSteps.push(obA.take(1).do(() => { log("a"); }).ignoreElements());
aSteps.push(obB.take(1).do(() => { log("b"); }).ignoreElements());
aSteps.push(obC.take(1).do(() => { log("c"); }));
let steps: Rx.Observable<any> = Rx.Observable.concat<any>(aSteps);
var source = steps
.takeUntil(Rx.Observable.timer(100 * values.length));
var subscription = source.subscribe(
function (x) {
log("Next: " + x);
},
function (err) {
divResult.innerHTML += "<div style='background-color: green'>Error: " + err + "</div>";
},
function () {
divResult.innerHTML += "<div style='background-color: green' > " + 'Completed' + "</div>";
});
}
stop() {
clearTimeout(this.timerToken);
}
}
window.onload = () => {
var app = new App1(document.getElementById('divStream'), document.getElementById('divResult'));
app.start();
};
回答1:
This seems to be working, it uses a simple state machine and is generalizable to recognize any basic regular expression. The regular expression recognized here is *abc* :
function noop () {}
var keyUp$ =
Rx.Observable.fromEvent(ta_input, 'keyup')
.map(function(ev){return ev.keyCode});
var stateMachine$ = keyUp$
.scan(function (state, keyCode) {
if (String.fromCharCode(keyCode) === state.password[state.index]) {
state.index++;
if (state.index === state.password.length) {
state.found = true;
}
}
else {
state.index = 0;
state.found = false;
}
return state;
}, {password : 'ABC', index : 0, found: false})
.filter(function (state){return state.found})
.take(1)
stateMachine$.subscribe(noop)
To check it, run the jsffidle, and type abc in the textarea. For some reason, your password has to be in caps, but that part should be easy to fix. When the abc
substring is detected, the stateMachine$
observable emits found
and completes.
回答2:
I believe you are looking for bufferWithCount.
Check out this spec from RxJSNext (renamed bufferCount in next):
it('should emit full buffer then last partial buffer if source completes', function () {
var e1 = hot('--a^-b--c--d--e--|');
var e1subs = '^ !';
var expected = '--------y-----(z|)';
expectObservable(e1.bufferCount(3)).toBe(expected, {
y: ['b', 'c', 'd'],
z: ['e']
});
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
来源:https://stackoverflow.com/questions/34890358/recognizing-and-exact-series-of-events-in-rxjs