3

I have an Angular component that lazy loads data to an Observable<Item[]> through a BehaviorSubject<Item[]> when the scroll position of a container reaches the bottom.

The needed properties and the init() function that initializes the list of items look like

private _itemList: BehaviorSubject<Item[]>;
private _done = new BehaviorSubject(false);
private _loading = new BehaviorSubject(false);
private _page: number;
itemList: Observable<Item[]>;

init() {
    this._page = 0;
    this._itemList = new BehaviorSubject([])
    this.mapAndUpdate();
    this.itemList = this._itemList.asObservable()
    .scan((acc, val) => {
        return acc.concat(val);
    });
}

and the function that does the actual fetching of data

mapAndUpdate() {
    if (this._done.value || this._loading.value) {
        return;
    }

    this._loading.next(true);
    const offset = this._page * 20;
    const limit = 20;
    return this.service.getItems(limit, offset)
    .do(res => {
        this._itemList.next(res.items);
        this._loading.next(false);
        if (!res.items.length) {
            this._done.next(true);
        }
    }).subscribe();
}

In the same component, I'm subscribing to realtime push events that can emit new items which should be added to the beginning of the array instead of the end of the array.

subscribeToItemsChannel() {
    this.pusherService.getItemsChannel().bind('new-item', (res: any) => {
        const item = res.item as Item;

        // Emitting the item with next() will only add it to the end of the array
        this._itemList.next([item]);
    });
}

I've tried to use a boolean value shouldPrepend that I set in the realtime function handler together with

.scan((acc, val) => {
    if(this._shouldPrepend) {
        return val.concat(acc);
    } else {
        return acc.concat(val);         
    }
});

which does add the new item to the beginning, but also messes up the order of the rest of the array and it doesn't feel like the correct rxjs way to do it either.

How can I at any given random moment prepend an object to an Observable<array>?

Here is a JSBin that better explains the problem.

2 Answers 2

3

I understand using a special this._shouldPrepend variable seems weird because it breaks the "pure functions" paradigm.

So personally I'd emit objects that contain information whether I what to append them or prepend.

enum Action { Append, Prepend };

...

numberList = bSubject
  .scan((acc, val) => val.action === Action.Prepend
    ? [val.value, ...acc]
    : [...acc, val.value]
  );

...

setInterval(() => { 
  console.log('Fetching data... Numberlist is now: ');
  bSubject.next({ action: Action.Append, value: i});
  i++;
}, 3000);

Your updated demo: http://jsbin.com/wasihog/edit?js,console

Eventually if you don't want to change the way you emit values you can have two merged Subjects. One that prepends values and another one that appends values.

const append$ = new Subject();
const prepend$ = new Subject();

Observable.merge(
  append$.map(value => ({ action: Action.Append, value })),
  prepend$.map(value => ({ action: Action.Prepend, value })),
)

You can see that you'll emit like append$.next(1) and the value will be wrapped with an object that tells scan what to do with it.

Sign up to request clarification or add additional context in comments.

2 Comments

Great suggestions, I'm gonna try to adapt it to my actual need and see if it works! :) Thanks!
@martin Being more used to Redux in normal cases, I went with the first suggestion of including an action. Great input, thanks alot!
1

I'm a bit late to this one, but it looks like you want startWith.

//emit (1,2,3)
const source = of(1, 2, 3);

//start with 0
const example = source.pipe(startWith(0));

//output: 0,1,2,3
const subscribe = example.subscribe(val => console.log(val));

This code sample is from https://www.learnrxjs.io/operators/combination/startwith.html

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.