GithubHelp home page GithubHelp logo

Comments (11)

NetanelBasal avatar NetanelBasal commented on May 22, 2024 2

@david-bulte I'm working a prototype that improves the transaction API. First, a new operator:

  getMovies() {
    return timer(1000).pipe(
      mapTo(movies),
      withTransaction(response => {
        this.actorsStore.set(response.entities.actors);
        this.genresStore.set(response.entities.genres);
        const movies = {
          entities: response.entities.movies,
          ids: response.result
        };
        this.moviesStore.set(movies);
      })
    );
  }

The second is a built-in solution for the case you mentioned, something like:

  selectActorNames(): Observable<Actor[]> {
    return combineLatest(this.movieQuery.selectActive(), this.selectAll({asObject: true})).pipe(
      batch(),<====
      filter(([movie, actors]) => !!movie && !!actors),
      map(([movie, actors]: [Movie, any]) => {
       return movie.actorIds.map(actorId => actors[actorId].name)
      })
    );
  }

from akita.

david-bulte avatar david-bulte commented on May 22, 2024 1

Here's an example: https://stackblitz.com/edit/angular-rqbnpv.

Have a look at ActorQuery's selectActorNames() (see below) which combines the Actor and Movies store. Depending on whether we first add the Actors and call the MovieStore.setActive() method afterwards or vice versa, the selectActorNames() method succeeds or fails. This is because when we first call the setActive() method, the selector is immediately triggered, and at that time we don't find the corresponding Actors.

Ideally the selectors should only be triggered after each 'tick'/event? I dunno. Or otherwise use the transaction mechanism for this?

actor.query.ts

  selectActorNames(): Observable<Actor[]> {
    return combineLatest(this.movieQuery.selectActive(), this.selectAll({asObject: true})).pipe(
      filter(([movie, actors]) => !!movie && !!actors),
      map(([movie, actors]: [Movie, any]) => {
       return movie.actorIds.map(actorId => actors[actorId].name)
      })
    );
  }

movie.service.ts

  load(id: string) {
    this.mockHttp(id).subscribe(({movie, actors}) => {      
        this.movieStore.add(movie);

        // this is ok
        // this.actorStore.add(actors);
        // this.movieStore.setActive(movie.id);

        // this is not
        this.movieStore.setActive(movie.id);
        this.actorStore.add(actors);
    })
  }

from akita.

NetanelBasal avatar NetanelBasal commented on May 22, 2024 1

As I mentioned in my article, you should use the auditTime operator:

  selectActorNames(): Observable<Actor[]> {
    return combineLatest(this.movieQuery.selectActive(), this.selectAll({asObject: true})).pipe(
      auditTime(0),
      filter(([movie, actors]) => !!movie && !!actors),
      map(([movie, actors]: [Movie, any]) => {
       return movie.actorIds.map(actorId => actors[actorId].name)
      })
    );
  }

from akita.

NetanelBasal avatar NetanelBasal commented on May 22, 2024

@transaction only optimized updates on the same store. So for example, if you call the following code:

@transaction
method() {
  this.storeOne.setActive(1);
  this.storeOne.add({ name: 'qwe' });
}

Subscribers to the store will get one notification instead twice (one for each call update).

If it answers your question, please close the issue. Thanks.

from akita.

david-bulte avatar david-bulte commented on May 22, 2024

Hi @NetanelBasal. Thanks for the great framework! Do you consider adding this in a future release?

The use case I'm thinking of is when you have 2 related entities, say Movie and Actor, where Movie has a field actorIds. There is a selector that returns the Actors of the "active" Movie. This goes well when you first add the Actors entities and then sets the active Movie. When you do it the other way around though things fall apart.

So there is a workaround, but it would be handy to do these changes in an atomic way.

from akita.

NetanelBasal avatar NetanelBasal commented on May 22, 2024

Thanks, can you add a concrete example (stackblitz) so we can look into this?

from akita.

NetanelBasal avatar NetanelBasal commented on May 22, 2024

@david-bulte check the new functionality here:

https://stackblitz.com/edit/angular-brebji?file=src%2Fapp%2Factor.query.ts

It's still experimental so let me know if there any issues.

from akita.

david-bulte avatar david-bulte commented on May 22, 2024

Thanks. This is working fine.

Some more thoughts, some may be relevant, others probably out of scope:

  • What's not (yet?) working are transactions spanning multiple async operations:
  load(id: string) {
    this.mockHttp(id).pipe(
      withTransaction(({ movie, actors }) => {
        this.movieStore.add(movie);
        this.movieStore.setActive(movie.id);
        // this.actorStore.add(actors);
        this.addActors(actors);
      }),
    ).subscribe()
  }

  addActors(actors) {
    timer(500).pipe(
// ---> I also tried with nested transactions <---
//      withTransaction(() => {
//        this.actorStore.add(actors);
//      }),
    ).subscribe(() => {
        this.actorStore.add(actors);
    })
  }

  • I like the waitForTransaction() selector, it clearly communicates what is going on. On the other hand, one could argue that the selectors shouldn't be triggered as long as the transaction hasn't been committed. Selectors shouldn't know whether something was added in a transaction or not (separation of concerns). Would filtering on a transaction state help? Something along these lines?
  const tx$ = new BehaviorSubject(false);

 _select<R>(project) {
    return combineLatest(this.store.asObservable(), tx$).pipe(
      filter(([state, tx]) => !!tx),
      map(([state]) => state)
      map(project),
      distinctUntilChanged()
    );
  }

from akita.

NetanelBasal avatar NetanelBasal commented on May 22, 2024

What's not (yet?) working are transactions spanning multiple async operations:

Transactions are only for synchronous operations; we can't know that all the async operations are done. (unless we use zone js and we don't want to)

Selectors shouldn't know whether something was added in a transaction or not (separation of concerns).

The waitForTransaction is relevant only for a combineLatest/merge functionality. Adding it per selector won't help.

from akita.

NetanelBasal avatar NetanelBasal commented on May 22, 2024

If you have a nice suggestion for a solution to your first issue, share with us.

from akita.

hgoebl avatar hgoebl commented on May 22, 2024

I would say transactions are not necessary over async operations. This can be achieved by waiting for both operations to complete and then manipulate the store.

But manipulating multiple stores so observables only fire once is very important in order to avoid ripple-effects. Otherwise, every selector spanning multiple stores has to use the auditTime operator. Not sure, but I can imagine that this also might add problems (values from single store access are rendered and next tick the things from multiple stores force a re-rendering.

I don't have a stackblitz and just came over this question because we're currently choosing a state mgmt solution (competitors are NGXS and NgRx).

from akita.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.