Creating a unique ngrx operator from scratch that modifies the source observable and outputs its type

I developed a custom operator called waitFor that is being used in my effects like this:

public effect$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
      ofType(myAction),
      waitFor<ReturnType<typeof myAction>>([anotherAction]),
      ...etc
    );
  });

This operator basically waits for specific actions to be dispatched before continuing execution based on correlationId's. However, the main focus here is different.

Although ofType takes the source observable and uses it as the return type, I'm having difficulty achieving the same behavior in my implementation. As shown above, I am using

ReturnType<typeof myAction>>
and the following in my waitFor method:

export function waitFor<A extends Action>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {

Currently, when I call waitFor like this:

public effect$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
      ofType(myAction),
      waitFor([anotherAction]),
      ...etc
    );
  });

The inferred type is Action, but I want it to default to

ReturnType<typeof theSourceObservable>
. To achieve this, I believe I would need something like this in my waitFor method:

export function waitFor<A extends ReturnType<typeof sourceObservable?!>>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {

This is how the waitFor function is implemented:

export function waitFor<A extends Action>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {
  return (source$) => {
    return source$.pipe(
      switchMap((action: A & { correlationId: string}) => {
        // Use zip() to wait for all actions 
        // Omit map((action) => action)
        // So the original action is always returned
      })
    );
  };
}

According to the ofType source code, it seems like I need to incorporate Extract.

Update

An example of StackBlitz can be found here

Answer №1

While this code compiles, it's uncertain if it fulfills your requirements.

public effect3$: Observable<Action> = createEffect(() => {
  const a:Action[]= []

  return this.actions$.pipe(
    ofType(doSomething),
    this.someCustomOperatorReturningStaticTypes(),
    this.thisWontWork(a),
    tap(({aCustomProperty}) => {
      // The type is inferred
      console.log(aCustomProperty);
    }),
  )
});

private thisWontWork<A extends Action>(actionsToWaitFor$: Action[]): OperatorFunction<A, A> {
  return (source$) => {
    return source$.pipe(
      tap(() => {
        console.log('Should work')
      })
    )
  }
}

I encountered difficulties running it in StackBlitz, any ideas on how to proceed?

Hope this insight is valuable

Answer №2

After stumbling upon this question, I felt compelled to provide some additional insights for those still seeking answers or anyone who may come across this dilemma in the future!

Initially - it seems like you're overcomplicating things when it comes to the return type.

If your goal is to infer the type of the value passed in (for instance, returning your action as the same type it is), all you need to do is return the generic that was initially passed in, just like this:

function foo<A>(val: A): A {
  return val;
}

Simply put, if I pass a number into foo(), I will receive a number back. Likewise, if I pass a specific action type, the same principle applies. Hence, using

type OperatorFunction<A, A>
suffices - same input yields the same output.

Secondly - how does ofType function?

The crucial aspect here is narrowing down a set of actions.

While createAction streamlines this process, let's create one manually for illustrative purposes.

type FooAction = { type: 'FOO_ACTION' };
type BarAction = { type: 'BAR_ACTION' };

// This Action type is constructed by you using createAction
type MyActions = FooAction | BarAction;

Next, we require a type that will take our actions (MyActions) and narrow them down based on their type.

type NarrowActionType<T extends { type: any }, K extends string> = T extends { type: K }
  ? T
  : never;

We can confirm its functionality with the following test:

type NarrowedActionTest = NarrowActionType<MyActions, 'FOO_ACTION'>;
...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to inject services into non-service class instances in Angular 2?

Here is my current approach, but I'm curious about the recommended practice for working with Angular2? ... class MultitonObject { _http: Http; constructor (appInjector: Injector) { this._http = appInjector.get(Http); } } var ap ...

How can I show distinct values in the Angular Material dropdown menu?

I am currently working on implementing a feature where I need to show unique options for the select using angular material. Below is what I have so far: where appitem is an array of items. <mat-form-field> <mat-select placeholder="Select app ...

Tips for maintaining consistent column width when scrolling down (column juggling) within a table in your Angular application

Take a look at this preview of the app I'm currently developing. https://stackblitz.com/edit/angular-ivy-3mrzkr In order to handle the large amount of data in my real app, I needed to incorporate a third-party scrolling module. After testing out cdk ...

Is there a way for me to understand the connection between `FormsModule` and `ngModel`?

After following the tutorial on angular hero editor, I came across a puzzling issue regarding FormsModule. It seems that the demo does not function properly without importing FormsModule first: https://angular.io/tutorial/toh-pt1#the-missing-formsmodule ...

transferring data between components in a software system

I received a response from the server and now I want to pass this response to another component for display. I attempted one method but ended up with undefined results. You can check out how I tried here: how to pass one component service response to other ...

I encountered a TypeScript error in React Native when attempting to use "className" with TypeScript

Although I've been using React for a while, React Native is new to me. I recently started using tailwind with import { View, Text } from "react-native"; import React from "react"; export default function Navigation() { return ...

Tips for injecting scripts into the head tag after an Angular component has been loaded

Currently, I am facing an issue with a script tag containing a Skype web control CDN. The script has been placed in the head section of my index.html file, but it is being called before the component that needs it has finished loading. Does anyone have a ...

Unleashing the Potential of a Single Node Express Server: Hosting Dual Angular Apps with Distinct Path

I have successfully managed to host two separate angular applications (one for customers and one for company staff) on the same node server, under different paths. The setup looks like this: // Serve admin app app.use(express.static(path.resolve(__dirname, ...

Unable to retrieve files from public folder on express server using a React application

The Issue When trying to render images saved on the backend using Express, I am facing a problem where the images appear broken in the browser. Despite looking for solutions to similar issues, none have resolved the issue for me. Specifics In my server.t ...

Ways to retrieve a json file within Angular4

Seeking guidance on accessing the data.json file within my myservice.service.ts file. Any suggestions on how to accomplish this task? Overview of directory structure https://i.stack.imgur.com/WiQmB.png Sample code from myservice.service.ts file ht ...

Steps to automatically set the database value as the default option in a dropdown menu

I'm in need of assistance with a project that I'm working on. To be completely honest, I am struggling to complete it without some help. Since I am new to Angular and Springboot with only basic knowledge, I have hit a roadblock and can't mak ...

Discover the effective method in Angular to display a solitary password validation message while dealing with numerous ones

Here is the pattern we use to validate input fields: The length of the input field should be between 6 and 15 characters. It should contain at least one lowercase letter (a-z). It should contain at least one uppercase letter (A-Z). It should contain at le ...

angular index.html code displayed

I successfully deployed an Angular app on a server in the past without any issues. The Angular version is 6.1.1, Angular CLI version is 6.2.9, npm version is 6.13.4, and node version is 10.18.0 for both local and server environments. The server is running ...

Passing data between two distinct components when a button is clicked within Angular 4, all while ensuring the route URL remains unchanged

Having recently started working with Angular 4, I encountered an issue when trying to pass data from one component to another without using ActivatedRoute to send the data via the URL. In my searchOption.component.html file, the Submit button code looks l ...

AG Grid is displaying improperly within Angular

I am facing an issue with using ag-grid in my project. The output is not as expected and I am unsure of what the problem might be. Here is a snippet of my HTML code: <p>user-access works!</p> <ag-grid-angular style="width: 500px; height: 50 ...

What is the best way to detect object changes in typescript?

Having an object and the desire to listen for changes in order to execute certain actions, my initial approach in ES6 would have been: let members = {}; let targetProxy = new Proxy(members, { set: function (members, key, value) { console.log(k ...

The proxy request gets delayed unless I utilize the http-proxy-middleware

Here is the code for a provider: @Injectable() export class GameServerProxyService { private httpProxy: httpProxy; constructor(@Inject(GameServerDetailsService) private gameServiceDetailsService: GameServerDetailsService) { this.httpP ...

Integrating router-outlet into Angular 2 component affects ngModel functionality

Currently, I am experimenting with angular 2 beta 9 and have encountered an issue that I would like some help with. In my component, I have bound an input field using the following code: [(ngModel)]="email" (ngModelChange)="changedExtraHandler($event)" ...

Testing Angular 4 routing with router.url

Is there a way to simulate router.url in Angular 4 unit testing? In my component, I'm using router.url in the ngOnint function but in my test, the value for router.url is always '/'. ...

Building a Custom Dropdown Select List with FormControlName and ControlValueAccessor in Angular 8

Does anyone have a working solution for creating a Material dropdown wrapper (mat-select dropdown) that can be used with formControlName? If so, could you please share a Stackblitz demo of your implementation? Here are the requirements: Should work seam ...