0
votes

Données partagées entre plusieurs observables RXJS

Je réécris du code pour utiliser RXJS et le tube asynchrone angulaire. Le code est assez simple, nous allons ajouter et supprimer des éléments d'un tableau. Existe-t-il un meilleur moyen d'accomplir la manipulation d'un tableau partagé que d'utiliser le jeu de clics pour stocker l'état dans un BehaviorSubject?

list$ | async 

EDIT: Le code de l'interface utilisateur utilise un tube asynchrone,

this.listSubject = new BehaviorSubject(['a','b','c']);
this.addSubject = new Subject<string>();
this.addAction$ = this.addSubject.asObservable().pipe(
            withLatestForm(this.listSubject),
            map(([itemToAdd, list]) => {list.push(itemToAdd); return list;}),
            tap((data) => this.listSubject.next(data))
        );
this.removeSubject = new Subject<number>();
this.removeAction$ = this.removeSubject.asObservable().pipe(
            withLatestForm(this.listSubject),
            map(([indexToRemove, list]) => {list.splice(indexToRemove, 1); return list;}),
            tap((data) => this.listSubject.next(data))
        );
this.list$ = merge(this.addAction$, this.removeAction$);


2 commentaires

C'est une conception terrible, vous êtes très susceptible de voir des exceptions ExpressionChangedAfterItHasBeenCheckedError sur la console, car la détection des modifications peut gérer les modifications apportées par la mutation des données en taps comme ça.


J'utilise ChangeDetectionStrategy.OnPush au lieu de la stratégie par défaut.


4 Réponses :


0
votes

Votre code a l'air vraiment sympa, la seule chose que je pourrais penser à ajouter serait l'immuabilité. De cette façon, vous obtiendrez une nouvelle liste chaque fois que vous y ajoutez ou supprimez des éléments:

    this.addAction$ = this.addSubject.asObservable().pipe(
                withLatestForm(this.listSubject),
                map(([itemToAdd, list]) => [...list, itemToAdd]),
                tap((data) => this.listSubject.next(data))
            );

    this.removeAction$ = this.removeSubject.asObservable().pipe(
                withLatestForm(this.listSubject),
                map(([indexToRemove, list]) =>
[...list.slice(0, indexToRemove), ...list.slice(indexToRemove + 1)]),
                tap((data) => this.listSubject.next(data))
            );


0 commentaires

2
votes

De manière générale, la plupart des utilisations de tap concerneront les hard liners RxJS.

La plus grande préoccupation que j'ai est que vous devriez vraiment modifier l'état de votre application (le sujet du comportement) avec un abonnement et non un robinet. Actuellement, vous n'affichez aucun abonné à list $ . Si personne ne s’abonne, votre tap ne fonctionnera jamais. D'un autre côté, s'il y a deux abonnés à list $ , vos taps fonctionneront deux fois pour chaque événement! Vous pourriez atténuer cela avec un publier quelque part mais je pense que ce serait beaucoup plus propre si vous aviez juste ...

this.listSubject = new BehaviorSubject(['a','b','c']);
this.addSubject = new Subject<string>();
this.addSubject.subscribe(itemToAdd => {
  const currentValue = this.listSubject.value;
  currentValue.push(itemToAdd);
  this.listSubject.next(currentValue);
});
this.removeSubject = new Subject<number>();
this.removeSubject.subscribe(indexToRemove => {
  const currentValue = this.listSubject.value;
  currentValue.splice(indexToRemove, 1);
  this.listSubject.next(currentValue);
});

Le second (légèrement la moindre) préoccupation que j'ai est que vous réémettez votre liste à partir de plusieurs sujets. Vous avez maintenant trois sujets, dont chacun peut émettre la même liste. Cela signifie que vous avez trois sources potentielles de vérité. Vous violez également CQS (séparation des requêtes de commande). Quand quelque chose appelle addItem , il ne devrait pas avoir besoin de saisir la nouvelle valeur de liste. Au lieu de cela, tout ce qui consomme la liste obtiendra simplement la mise à jour car le sujet de votre liste est mis à jour.

Lecture supplémentaire: Dans votre application, vous avez l'état (la liste) et les événements qui peuvent modifier l'état. Ou, plus généralement, des «actions» qui peuvent modifier votre état. Dans un outil comme ngrx , vous verrez des concepts comme "stores" (votre état) et "actions" (vos événements) pour décrire ce modèle. Les sujets de comportement sont une alternative légère à quelque chose comme ngrx store. Au fur et à mesure que votre application devient de plus en plus complexe, vous gagnerez peut-être beaucoup à lire des outils comme ngrx store.


0 commentaires

1
votes

Pourquoi n'utiliseriez-vous pas de fonctions?

list$ = new BehaviorSubject(['a','b','c']);

add(val) {
  this.list$.next([...this.list$.value, val]);
}

remove(val) {
  this.list$.next(...this.list$.value.filter(v => v !== val));
}

Votre code n'est pas clair, qu'est-ce que souscrire à toutes ces observables. Vous auriez besoin d'abonnements à vos observables d'action pour déclencher n'importe quelle fonctionnalité. Vous faites muter des objets.


0 commentaires

3
votes

Quelque chose comme ça fonctionne:

<div>{{ list$ | async}}</div>

REMARQUE: j'ai fait un stackblitz de votre code ici: https://stackblitz.com/edit/angular-array-processing-deborahk

J'ai créé un flux d'action unique qui a émis un objet d'action avec l'élément à ajouter / supprimer et si l'action est une suppression ou non. (Vous pouvez changer cela en une énumération pour ajouter et supprimer)

J'ai ensuite utilisé scan pour conserver l'ensemble d'éléments au fil du temps et simplement ajouter ou supprimer de cette liste d'éléments .

Votre code se trouve dans le fichier app.component.ts

Le code révisé se trouve dans le code hello.component.ts > fichier.

Est-ce que cela fonctionne pour votre scénario?

REMARQUE: Pour ceux qui ont dit que vous devez vous abonner ... vous ne le faites pas si vous utilisez le tube asynchrone. Mon interface utilisateur ressemble à ceci:

export class HelloComponent {
  actionSubject = new Subject<Action>();
  action$ = this.actionSubject.asObservable();

  originalList$ = of(["a", "b", "c"]);

  list$ = merge(this.originalList$, this.action$).pipe(
    scan((acc: string[], action: any) => {
      if (action.isDelete) {
        return acc.filter(item => item !== action.item);
      } else {
        return [...acc, action.item];
      }
    })
  );

  onAdd() {
    this.actionSubject.next({ item: "z", isDelete: false });
  }

  onRemove() {
    this.actionSubject.next({ item: "b", isDelete: true });
  }
}

export interface Action {
  item: string;
  // This could instead be an Enum of operations
  isDelete: Boolean;
}


1 commentaires

Pensez que cette idée fonctionnera bien, car elle peut être étendue pour prendre en charge plus d'opérations sur la baie. J'ai pris votre interface Action et l'ai étendue pour avoir une fonction et des propriétés args. stackblitz.com/edit/ …