dev_054
dev_054

Reputation: 3728

How to not emit an event on initialization using ngrx/component-store sample?

I'm creating a similar component to MatPaginator, but using ComponentStore following this docs and it seems to work perfectly, except for the fact of @Output page is emmitting on initialization which is unexpected. The expected behavior is to emit only when the user changes a page, like in the Material version.

Here's the code that matters (it's identical to the linked docs):

PaginatorStore:

@Injectable()
export class PaginatorStore extends ComponentStore<PaginatorState> {
  private readonly pageIndexChanges$ = this.state$.pipe(
    map(({ pageIndex }) => pageIndex),
    pairwise()
  );
 
  readonly page$: Observable<PageEvent> = this.select(
    this.pageIndexChanges$,
    this.select((state) => [state.pageSize, state.length]),
    ([previousPageIndex, pageIndex], [pageSize, length]) => ({
      pageIndex,
      previousPageIndex,
      pageSize,
      length,
    }),
    { debounce: true }
  );
}

PaginatorComponent:

@Component({ providers: [PaginatorStore] })
class Component {
  @Output() readonly page = this.paginatorStore.page$;
}

Note that I've also opened an issue in the repo, but I don't get any answer until the moment.

ComponentStore DEMO

Material DEMO

Upvotes: 2

Views: 1196

Answers (1)

Aleš Doganoc
Aleš Doganoc

Reputation: 12062

The ngrx store behaves like a BehaviorSubject which means that on subscription you will always get the current state. In the start this is the initial state you set when you create the store.

If you want to start from the next change you can add the skip operator and skip the first value this way you will never get the current state and receive only the events after.

@Component({ providers: [PaginatorStore] })
class Component {
  @Output() readonly page = this.paginatorStore.page$.pipe(skip(1));
}

For your case I think you should go with a more complicated solution since Angular expects that @Output properties use the EventEmitter type. The event emitter is also a Subject so you can subscribe directly to the store Observable and the values will be propagated automatically.

Here is a sample implementation for this scenario.

@Component({ providers: [PaginatorStore] })
class Component implements OnDestroy {
  @Output() readonly page = new EventEmitter<PageEvent>();

  private destroyed = false;

  constructor(private readonly paginatorStore: PaginatorStore) {
    this.paginatorStore.page$.pipe(skip(1), takeWhile(() => !this.destroyed)).subscribe(this.page);
  }

  ngOnDestroy(): void {
    this.destroyed = true;
  }
}

You can see I added also an OnDestroy implementation to prevent the subscription from remaining after the component gets destroyed because this can cause memory leaks.

Here is also a link to a fork of your StackBlitz so you can see it in action.

Upvotes: 2

Related Questions