1

There is an array in public users = new BehaviorSubject<User[]>([]).

I want to delete element from this observable and refresh it.

My solution is:

const idRemove = 2;
this.users.next(this.user.getValue().filter((u) => u.id !== idRemove)

But I seem I use wrong way of using RXJS

3 Answers3

1

Toward Idiomatic RxJS

Using subscribe instead of .value.

interface User {
  id: number
}

const users$ = new BehaviorSubject<User[]>([
  {id:1}, 
  {id:2}, 
  {id:3}
]);

function removeId(idRemove: number) {
  users$.pipe(
    take(1),
    map(us => us.filter(u => u.id !== idRemove))
  ).subscribe(
    users$.next.bind(users$)
  );
}

users$.subscribe(us =>
  console.log("Current Users: ", us)
);

removeId(2);
removeId(1);
removeId(3);

Output:

Current Users:  [ { id: 1 }, { id: 2 }, { id: 3 } ]
Current Users:  [ { id: 1 }, { id: 3 } ]
Current Users:  [ { id: 3 } ]
Current Users:  []
Mrk Sef
  • 7,557
  • 1
  • 9
  • 21
0

To handle state within RxJS pipes you can use the Scan operator

Useful for encapsulating and managing state. Applies an accumulator (or "reducer function") to each value from the source after an initial state is established -- either via a seed value (second argument), or from the first value from the source.

const { Subject, merge } = rxjs;
const { scan, map } = rxjs.operators;

// This function is used to apply new users to the state of the scan
const usersFn = users => state => users
// This function is used to remove all matching users with the given id from the state of the scan
const removeFn = removeId => state => state.filter(user => user.id !== removeId)

// This Subject represents your old user BehaviorSubject
const users$$ = new Subject()
// This Subject represents the place where this.users.next(this.user.getValue().filter((u) => u.id !== idRemove) was called
const remove$$ = new Subject()

// This is your new user$ Observable that handles a state within its pipe. Use this Observable in all places where you need your user Array instead of the user BehaviorSubject
const user$ = merge(
  // When users$$ emits the usersFn is called with the users argument (1. time)
  users$$.pipe(map(usersFn)),
  // When remove$$ emits the removeFn is called with the removeId argument (1. time)
  remove$$.pipe(map(removeFn))
).pipe(
  // Either the usersFn or removeFn is called the second time with the state argument (2. time)
  scan((state, fn) => fn(state), [])
)

// Debug subscription
user$.subscribe(console.log)

// Test emits
users$$.next([
  {id: 1, name: "first"},
  {id: 2, name: "second"}
])
remove$$.next(2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>

Ben Lesh (main Contributor of RxJS) wrote an anser about why not to use getValue in RxJS: The only way you should be getting values "out of" an Observable/Subject is with subscribe!

Jonathan Stellwag
  • 3,843
  • 4
  • 25
  • 50
-1

Using getValue() is discouraged in general for reasons explained here even though I'm sure there are exception where it's fine to use it. So a better way is subscribing to get the latest value and then changing it:

this.users
  .pipe(take(1)) // take(1) will make sure we're not creating an infinite loop
  .subscribe(users => {
    this.users.next(users.filter((u) => u.id !== idRemove);
  });
martin
  • 93,354
  • 25
  • 191
  • 226