将修改后的数组发布到Observable

时间:2017-09-22 16:17:37

标签: javascript angular typescript rxjs reactive-programming

任务

假设我们实现了Angular服务,并且需要向世界发布left | right | result ======================= $null | $true | $false $null | $false | $true (!) <--- not what you intended

Observable<number[]>

我们希望订阅者:

  1. 在订阅时收到最后一个值
  2. 每次更改并发布时都会收到整个修改过的数组
  3. 从#1开始,numbers: Observable<number[]>; 内部至少应该是Observable<number[]>

    但#2怎么样?假设我们需要实现一个方法BehaviorSubject<number[]>,只要我们需要更改和发布更改的数组就会调用它:

    publishNumbersChange()

    问题

    根据以前的项目实施发布修改后的数组的任务的RxJS 5模式是什么?

    因为我问它主要是因为目前我正在做Angular的东西,这是问题的第二部分:
    Angular(以及基于RxJS的类似框架)在提供private publishNumbersChange() { // Get current numbers array ... changeArray(); // Now publish changed numbers array ... } 哪个类型参数是随后发布更新数组的数组时会使用什么代码?
    他们只是分别保留当前发布的数组的副本吗?

    一些想法

    似乎单独存储底层数组,因此我们始终可以访问它,这是最简单的事情。但同时它看起来不像RxJS方式(需要在RxJS流之外有一个状态)。

    另一方面,我们可以做以下事情:

    Observable

    我在这里看到至少一个问题(不包括复杂性/可重用性):执行订阅回调和取消订阅之间的“竞争条件”。查看该代码,您无法确定是否实际执行了回调。所以它看起来也不是一种正确的方法。

1 个答案:

答案 0 :(得分:5)

听起来您可能正在寻找的操作员就是扫描。

let arraySubject = new BehaviorSubject([]);
let array$ = arraySubject.scan((fullArray, newValue) => fullArray.concat([newValue]), [])

扫描在可观察流中累积值随时间的变化,并且流中的每个项目将最后一个发射值和当前值作为参数。对它们执行一个函数,然后发出结果。上面的示例获取一个新值并将其附加到完整数组,第二个参数将其初始化为空数组。

这显然有点限制,因为它只做一件事,可能不够健壮。在这种情况下,你需要聪明:

let arraySubject = new BehaviorSubject([]);
let array$ = arraySubject.scan((fullArray, {modifier, payload}) => modifier(fullArray, payload), []);

现在你正在通过&#34;行动&#34;它有一个修饰符函数,它定义了你想要修改完整数组的方式,以及修饰符可能需要与完整数组一起进入修饰符函数的任何附加数据的有效负载

所以你可以这样做:

let modifier = (full, item) => full.splice(full.indexOf(item), 1);
arraySubject.next({modifier, payload: itemToRemove});

删除您发送的项目。您可以将此模式扩展为字面上的任何数组修改。

A&#34;陷阱&#34;虽然扫描是订阅者只从他们所订的时间获得累积值。所以,这将会发生:

let arraySubject = new BehaviorSubject([]);
let array$ = arraySubject.scan((fullArray, {modifier, payload}) => modifier(fullArray, payload), []);
let subscriber1 = array$.subscribe();
//subscriber1 gets []
let modifier = (full, val) => full.concat([val]);
arraySubject.next({modifier, payload:1});
//subscriber1 gets [1]
arraySubject.next({modifier, payload:2});
//subscriber1 gets [1,2]
let subscriber2 = array$.subscribe();
//subscriber2 gets [2]
arraySubject.next({modifier, payload:3});
//subscriber1 gets [1,2,3]
//subscriber2 gets [2,3]

看看那里发生了什么? behaviorubject中唯一存储的是第二个事件,而不是完整数组,scan正在存储整个数组,因此第二个订阅者只获得第二个动作,因为它在第一个动作期间没有订阅。所以你需要一个持久的订户模式:

let arraySubject = BehaviorSubject([]);
let arrayModifierSubject = new Subject();
arrayModifierSubject.scan((fullArray, {modifier, payload}) => modifier(fullArray, payload), []).subscribe(arraySubject);

并通过在arrayModifierSubject上调用next进行修改:

let modifier = (full, val) => full.concat([val]);
arrayModifierSubject.next({modifier, payload: 1});

您的订阅者从数组源获取数组:

subscriber1 = arraySubject.subscribe();

在此设置中,所有数组修改都通过修饰符主题,修改者主体将其广播到行为主体,行为主体为将来的订阅者存储完整数组并将其广播给当前订阅者。 behaviorubject(商店主题)持久地订阅修饰主题(动作主题),并且是动作主题的唯一订阅者,因此完整数组永远不会丢失,因为始终保持整个动作历史。

一些示例用法(使用上述设置):

// insert 1 at end
let modifier = (full, value) => full.concat([value]);
arrayModifierSubject.next({modifier, payload: 1});

// insert 1 at start
let modifier = (full, value) => [value].concat(full);
arrayModifierSubject.next({modifier, payload: 1});

// remove 1
let modifier = (full, value) => full.splice(full.indexOf(value),1);
arrayModifierSubject.next({modifier, payload: 1});

// change all instances of 1 to 2
let modifier = (full, value) => full.map(v => (v === value.target) ? value.newValue : v);
arrayModifierSubject.next({modifier, payload: {target: 1, newValue: 2}});

你可以将这些功能中的任何一个包装在&#34; publishNumbersChange&#34;功能。如何完全实现这取决于您的需求,您可以使用以下函数:

insertNumber(numberToInsert:number) => {
   let modifier = (full, val) => full.concat([val]);
   publishNumbersChange(modifier, numberToInsert);
}

publishNumbersChange(modifier, payload) => {
   arrayModifierSubject.next({modifier, payload});
}

或者您可以声明一个接口并创建类并使用它:

publishNumbersChange({modifier, payload}) => {
   arrayModifierSubject.next({modifier, payload});
}

interface NumberArrayModifier {
    modifier: (full: number[], payload:any) => number[];
    payload: any;
}

class InsertNumber implements NumberArrayModifier {
    modifier = (full: number[], payload: number): number[] => full.concat([payload]);
    payload: number;
    constructor(numberToInsert:number) {
        this.payload = numberToInsert;
    }
}

publishNumbersChange(new InsertNumber(1));

您还可以将类似功能扩展到任何阵列修改。最后一个原型:lodash是在这种类型的系统中定义修改器的巨大帮助

那么,这在角度服务环境中看起来怎么样?

这是一个非常简单的实现,不是高度可重用的,但其他实现可能是:

const INIT_STATE = [];
@Injectable()
export class NumberArrayService {
    private numberArraySource = new BehaviorSubject(INIT_STATE);
    private numberArrayModifierSource = new Subject();
    numberArray$ = this.numberArraySource.asObservable();

    constructor() {
        this.numberArrayModifierSource.scan((fullArray, {modifier, payload?}) => modifier(fullArray, payload), INIT_STATE).subscribe(this.numberArraySource);
    }

    private publishNumberChange(modifier, payload?) {
        this.numberArrayModifierSource.next({modifier, payload});
    }

    insertNumber(numberToInsert) {
        let modifier = (full, val) => full.concat([val]);
        this.publishNumberChange(modifier, numberToInsert);
    }

    removeNumber(numberToRemove) {
        let modifier = (full, val) => full.splice(full.indexOf(val),1);
        this.publishNumberChange(modifier, numberToRemove);
    }

    sort() {
        let modifier = (full, val) => full.sort();
        this.publishNumberChange(modifier);
    }

    reset() {
        let modifier = (full, val) => INIT_STATE;
        this.publishNumberChange(modifier);
    }
}

这里的用法很简单,订阅者只需订阅numberArray $并通过调用函数来修改数组。您可以使用此简单模式来扩展您喜欢的功能。这可以控制对数字数组的访问,并确保它始终以api和您的状态定义的方式进行修改,并且您的主题始终是同一个。

好的,但这是如何制作通用/可重复使用的?

export interface Modifier<T> {
    modifier: (state: T, payload:any) => T;
    payload?: any;
}

export class StoreSubject<T> {
    private storeSource: BehaviorSubject<T>;
    private modifierSource: Subject<Modifier<T>>;
    store$: Observable<T>;

    publish(modifier: Modifier<T>): void {
        this.modifierSource.next(modifier);
    }

    constructor(init_state:T) {
        this.storeSource = new BehaviorSubject<T>(init_state);
        this.modifierSource = new Subject<Modifier<T>>();
        this.modifierSource.scan((acc:T, modifier:Modifier<T>) => modifier.modifier(acc, modifier.payload), init_state).subscribe(this.storeSource);
        this.store$ = this.storeSource.asObservable();
    }
}

,您的服务变为:

const INIT_STATE = [];
@Injectable()
export class NumberArrayService {
    private numberArraySource = new StoreSubject<number[]>(INIT_STATE);
    numberArray$ = this.numberArraySource.store$;

    constructor() {
    }

    insertNumber(numberToInsert: number) {
        let modifier = (full, val) => full.concat([val]);
        this.numberArraySource.publish({modifier, payload: numberToInsert});
    }

    removeNumber(numberToRemove: number) {
        let modifier = (full, val) => full.splice(full.indexOf(val),1);
        this.numberArraySource.publish({modifier, payload: numberToRemove});
    }

    sort() {
        let modifier = (full, val) => full.sort();
        this.numberArraySource.publish({modifier});
    }

    reset() {
        let modifier = (full, val) => INIT_STATE;
        this.numberArraySource.publish({modifier});
    }
}