Giter VIP home page Giter VIP logo

effects's Introduction


A framework-agnostic RxJS effects implementation

@ngneat/effects commitizen PRs coc-badge semantic-release styled with prettier

πŸ‘‰ Play with the code on stackblitz

Effects

First, we need to initialize the the library by calling the initEffects() function:

import { initEffects } from '@ngneat/effects';

initEffects();

Actions are created by using the createAction or actionsFactory functions:

import { actionsFactory, createAction, props } from '@ngneat/effects';

// todos.actions.ts
export interface Todo {
  id: string;
  name: string;
}

export const addTodo = createAction('[Todos] Add Todo', props<{ title: string }>());

// We recommend using the actions factory to prefix each action 
// for better readability and debug purposes when using redux dev tools
export const todoActions = actionsFactory('todo');

// We can declare an action by passing it a type and an optional payload. 
export const loadTodos = todoActions.create('Load Todos')
export const addTodo   = todoActions.create('Add Todo', props<Todo>())

Next, we need to define the effects, and register them:

import { createEffect, registerEffects, ofType, tapResult } from '@ngneat/effects';

export const addTodo$ = createEffect((actions) =>
    actions.pipe(
      ofType(addTodo),
      switchMap(() => apiCall().pipe(
        tapResult(console.log, console.error)
      ))
    );
  )
);

registerEffects([addTodo$])

The tapResult operator safely handles the result. It enforces that the effect would still be running in case of error. Finally, we can dispatch actions using the dispatch function:

import { dispatch } from '@ngneat/effects';

dispatch(addTodo({ title: 'effects' }));

tapResult also let us specify a custom error and completed handler. If no custom error handling is specified, a possible error will be printed to the console.

Use with React

First, install the package: npm i @ngneat/effects-hook.

Now, we can use the useEffects hook and pass our effects:

import { useEffects } from '@ngneat/effects-hook';
import { dispatch }   from '@ngneat/effects';
import { useEffect }  from 'react';

export function TodosPage() {
  useEffects([loadTodos$, addTodo$]);

  useEffect(() => dispatch(loadTodos()), []);

  return (
    <button onClick = {() => dispatch(addTodo({ title: 'foo' }))}>
      Add
    </button>
  )
}

The effects we pass are tied to the component life cycle hook and will be destroyed with the component.

Use with Angular

First, install the package: npm i @ngneat/effects-ng.

Next, create the effect provider:

import { createEffect } from '@ngneat/effects';

@Injectable({ providedIn: 'root' })
export class TodosEffects {

  constructor(private todosApi: TodosApi) {}

  loadTodos$ = createEffect(actions => actions.pipe(
    ofType(loadTodos),
    switchMap((todo) => this.todosApi.loadTodos())
  ));
}

By default, the return value of an effect doesn't dispatch an action. You can get this behavior by passing the { dispatch: false } option as a second parameter.

Then we need to register effects manager by calling provideEffectsManager at the root level. Also to register effects at the root level we need to call provideEffect function:

import { provideEffectsManager, provideEffect } from '@ngneat/effects-ng';
import { TodosEffects } from 'todos/todos.effect.ts';

@NgModule({
  providers: [
    /**
     *  provideEffectsManager({ dispatchByDefault: true }),
     */
    provideEffectsManager(),
    provideEffect(TodosEffects),
  ]
})
export class AppModule {
}

-- OR --

bootstrapApplication(AppComponent, {
  providers: [
    /**
     *  provideEffectsManager({ dispatchByDefault: true }),
     */
    provideEffectsManager(),
    provideEffects(TodosEffects)
  ],
});

The provideEffectsManager function can take the global configuration. We can set the dispatchByDefault property to true for each effect to dispatch the resulting action. The default is set to false.

As stated above, this behavior can be overwritten on each effect.

In order to register lazily loaded effects use the provideEffect function on the envirenment that you need:

import { provideEffect } from '@ngneat/effects-ng';
import { PostsEffects } from "posts/posts.effect.ts"

@NgModule({
  providers: [
    provideEffect(PostsEffects),
  ]
})
export class LazyModule {
}

-- OR --

export ROUTES = [
  ...,
  {
    path: 'lazy',
    loadChildren: () => import('./lazy-route/lazy.routes').then(mod => mod.ROUTES),
    providers: [provideEffect(PostsEffects)],
  }
]

The actions can be dispatched by injecting the Actions provider:

import { Actions } from '@ngneat/effects-ng';

@Component(...)
export class AppComponent {
  constructor(private actions: Actions) {}

  ngOnInit() {
    this.actions.dispatch(loadTodos());
  }
}

Registering an effects class multiple times, either by forRoot(), forFeature(), or provideEffects(), (for example in different lazy loaded features) will not cause the effects to run multiple times.

Directive Effects

provideDirectiveEffects() and EffectsDirective serve to register effects on the component injector level. This means that effects will live as long as the component where effects are registered lives. Do not forget to call provideEffectsManager in the root providers.

import { provideDirectiveEffects, EffectsDirective, Actions } from '@ngneat/effects-ng';

@Component({
  ...,
  providers: [provideDirectiveEffects(TodosEffects)],
  hostDirectives: [EffectsDirective],
})
export class TodosComponent {
  constructor(private actions: Actions) {}

  ngOnInit() {
    this.actions.dispatch(loadTodos());
  }
}

If multiple components register the same effects via provideDirectiveEffects() & EffectsDirective it will not cause the effects to run multiple times. The effects will be running until the last component that registered these effects via provideDirectiveEffects() & EffectsDirective is destroyed. If the same effects were registered multiple times via forRoot(), forFeature(), provideEffects() and provideDirectiveEffects() & EffectsDirective then after the component is destroyed the effects will be still running.

Testing

In order to test effect classes and using the actions stream from parameter you can substitute the action stream by a custom created action stream. It's recommended to only use this feature for testing purposes.

describe("Effect test", () => {
  // use a custom action stream to replace the stream before each test
  let customActionsStream: Actions;

  beforeEach(() => {
    customActionsStream = new Actions();

    TestBed.configureTestingModule({
      providers: [
        provideEffectsManager({ customActionsStream }),
        provideEffect(EffectsOne),
      ]
    });
  })
})

Effect Functions

To use an effect function we first need to create it by using the createEffectFn function:

import { createEffectFn } from '@ngneat/effects';

export const searchTodoEffect = createEffectFn((searchTerm$: Observable<string>) => {
  return searchTerm$.pipe(
    debounceTime(300),
    switchMap((searchTerm) => fetchTodos({ searchTerm })),
  );
});

The createEffectFn function takes a callback function which is passed an Observable parameter and returns an Observable.

Use with React

First, install the package: npm i @ngneat/effects-hook.

We can register the effect in our component, and call it when we need:

import { useEffectFn } from '@ngneat/effects-hooks';

function SearchComponent() {
  const searchTodo = useEffectFn(searchTodoEffect);

  return <input onChange = {({ target: { value } }) => searchTodo(value) }/>
}

Every time the effect is called, its value is pushed into that Observable.

We can also register multiple effects:

function FooComponent() {
  const [addTodo, updateTodo, deleteTodo] = useEffectFn([
    addTodoEffect, updateTodoEffect, deleteTodoEffect
  ]);

  return ...
}

Use with Angular

First, install the package: npm i @ngneat/effects-ng.

Create an effect class, extends the EffectFn class and use the createEffectFn method to create your effects:

import { EffectFn } from '@ngneat/effects-ng';

export class TodosEffects extends EffectFn {

  searchTodo = this.createEffectFn((searchTerm$: Observable<string>) => 
    searchTerm$.pipe(
      debounceTime(300),
      switchMap((searchTerm) => fetchTodos({ searchTerm })),
    );
  );
}

Inject the effects provider in your component, and call it when you need:

@Component({
  providers: [TodosEffects],
})
export class TodosComponent {
  constructor(private todosEffects: TodosEffects) {
  }

  ngOnInit() {
    this.control.valueChanges.subscribe(searchTerm => {
      this.todosEffects.searchTodo(searchTerm);
    });
  }
}
Icons made by Freepik from www.flaticon.com

effects's People

Contributors

ericpoul avatar feitzi avatar netanelbasal avatar ntziolis avatar skorunka avatar theorlovsky avatar tobbiar avatar yackinn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

effects's Issues

Jest suits fail

Which @ngneat/effects-* package(s) are the source of the bug?

effects

Is this a regression?

No

Description

After updating to from v1.1.1 to v2.1.1 Jest suits started failing

`

SyntaxError: Cannot use import statement outside a module

  1 | import { Component } from '@angular/core';
> 2 | import {Actions} from "@ngneat/effects-ng";
    | ^

`

Here is the log output of a reproduction example:
https://github.com/frango9000/effects-jest/actions/runs/4332758127/jobs/7565462273

Also noticed that the output in node_modules folder is different:
v1.1.1
image
v2.1.1
image

Please provide a link to a minimal reproduction of the bug

https://github.com/frango9000/effects-jest

Please provide the exception or error you saw

FAIL effects-jest apps/effects-jest/src/app/app.component.spec.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     β€’ If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     β€’ If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     β€’ To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     β€’ If you need a custom transformation specify a "transform" option in your config.
     β€’ If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /home/runner/work/effects-jest/effects-jest/node_modules/@ngneat/effects/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { Subject, Observable, EMPTY } from 'rxjs';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      1 | import { Component } from '@angular/core';
    > 2 | import {Actions} from "@ngneat/effects-ng";
        | ^
      3 | import {loadApp} from "./app.effects";
      4 |
      5 | @Component({

      at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1495:14)
      at Object.<anonymous> (src/app/app.component.ts:2:1)
      at Object.<anonymous> (src/app/app.component.spec.ts:2:1)

Please provide the environment you discovered this bug in

"@ngneat/effects": "2.1.1",
    "jest": "29.x",

also on 

    "jest": "28.x",

Anything else?

No response

Do you want to create a pull request?

No

chore: format all the files with prettier and setup the pre-commit hook

Which @ngneat/effects-* package(s) are relevant/releated to the feature request?

Don't known / other

Description

Right now the codebase is inconsistent, which make it harder to contribute to the lib without affecting unrelated code

Proposed solution

I suggest formatting all the files with prettier now and setting up the pre-commit hook for any future changes

Alternatives considered

Trying to match the code style without any tools 😬

Do you want to create a pull request?

Yes

Dispatching effects throw an error

Which @ngneat/effects-* package(s) are the source of the bug?

effects

Is this a regression?

No

Description

Effects are throwing an error when dispatched.

When following the Documentation example:

loadTodos$ = createEffect(actions => actions.pipe( ofType(loadTodos), switchMap((todo) => this.todosApi.loadTodos()) ));

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/angular-mctwxe?file=src/main.ts

Please provide the exception or error you saw

ERROR
Error: Make sure to provide a valid action type or set the option {dispatch: false}

Please provide the environment you discovered this bug in

"@ngneat/effects": "2.1.0",
        "@ngneat/effects-ng": "3.0.0",

Anything else?

No response

Do you want to create a pull request?

Yes

Typo in docs regarding "Directive Effects"

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

No

Description

Currently the example for "Directive Effects" contains a typo.

Instead of hostDirectives the docs says hostDirective.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

No response

Anything else?

No response

Do you want to create a pull request?

Yes

Error handling with effect

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

No

Description

If an effect returns an unhandled exception, it can not be started again. For me its seems like a bug, or some kind of missing documentation to handle errors.
It's possible to use catchError, but there should be a more generic way to solve that problem?

From my submitted sample:
image

Please provide a link to a minimal reproduction of the bug

https://codesandbox.io/s/elfeffectserrorhandling-oo7qvf?file=/src/app/app.component.ts

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

No response

Anything else?

No response

Do you want to create a pull request?

Yes

effects-ng installation problems

There seems to be dependency discrepancies between the different Elf packages. Elf and other packages depend on an exact version of 15.2.4 for angular packages but Effects depends on exact versions of 15.1.4 for angular packages. Since updating Elf packages to the latest I cannot install without --force. Is it possible to either fix the differing exact version dependencies or use more flexible semver?

Discussed in #50

Originally posted by ericwfisher March 28, 2023
Anyone else here having issues installing @ngneat/[email protected] due to a reported dependency on @angular/[email protected]?

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: [email protected]
npm ERR! Found: @angular/[email protected]
npm ERR! node_modules/@angular/router
npm ERR!   @angular/router@"^15.2.4" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer @angular/router@"15.1.4" from @ngneat/[email protected]
npm ERR! node_modules/@ngneat/effects-ng
npm ERR!   @ngneat/effects-ng@"^3.1.0" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR! See ~/.npm/eresolve-report.txt for a full report.

npm ERR! A complete log of this run can be found in:
npm ERR!    ~/.npm/_logs/2023-03-28T17_54_27_983Z-debug-0.log

Not supported for @angular/[email protected]

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

Yes

Description

Hi, I'm using node version 18.14. I'm getting a problem after uprating my angular version 14 to 15. I saw it's a peer dependency.
Here is the screenshot.

Screen Shot 2023-03-04 at 5 49 13 PM

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

Node version 18.14.0 and npm version 9.3.1 angular cil 15.2.1

Anything else?

No response

Do you want to create a pull request?

No

can I use without inheritance in angular?

Which @ngneat/effects-* package(s) are relevant/releated to the feature request?

effects

Description

Is it possible to use it in angular without inheritance?

  • without inheritance
  • just create the function (without do nothing else) like pass it to register function and so on..

Here my suggestion:

@Injectable({ providedIn: 'root' })
export class TodosEffects {

  addTodo = createEffect((actions) =>
    actions.pipe(
      ofType(addTodo),
      tap(console.log)
    )
  );
}

@Component({
  ...
  providers: [TodoEffects]
})
export class Component {

 constructor(private todosEffects: TodoEffects) {}

  addTodo() {
    this.todosEffects.addTodo();
  }
}

Proposed solution

see in description

Alternatives considered

n/a

Do you want to create a pull request?

Yes

Effects NG: Build error, when using Directive Effects with angular 15

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

Yes

Description

When using the useDirectiveEffects function of @ngneat/effects-ng with angular 15 of, I get the following error:

Error: src/app/app.component.ts:9:21 - error NG1010: Host directive must be a reference
  Value could not be determined statically.

9     hostDirectives: [useDirectiveEffects(Test1Effect)]
                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  src/app/app.component.ts:9:22
    9     hostDirectives: [useDirectiveEffects(Test1Effect)]
                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Unable to evaluate this expression statically.
  src/app/app.component.ts:9:22
    9     hostDirectives: [useDirectiveEffects(Test1Effect)]
                           ~~~~~~~~~~~~~~~~~~~
    A value for 'useDirectiveEffects' cannot be determined statically, as it is an external declaration.


Error: src/app/app.module.ts:9:9 - error NG6001: The class 'AppComponent' is listed in the declarations of the NgModule 'AppModule', but is not a directive, a component, or a pipe. Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.

9         AppComponent,
          ~~~~~~~~~~~~

  src/app/app.component.ts:11:14
    11 export class AppComponent {
                    ~~~~~~~~~~~~
    'AppComponent' is declared here.

Please provide a link to a minimal reproduction of the bug

https://github.com/TobbiAR/effects-ng-directive-issue

Please provide the exception or error you saw

Error: src/app/app.component.ts:9:21 - error NG1010: Host directive must be a reference
  Value could not be determined statically.

Please provide the environment you discovered this bug in

I've attached the link to a minimal reproduction repostory with a single "app.component", on which the "hostDirectives" with "useDirectiveEffects" is used. The error also occurs, if I use the "useDirectiveEffects" on standalone components.

To reproduce the bug simply run "npm run start".

Anything else?

No response

Do you want to create a pull request?

No

Wrong import of 'provideEffects' (README)

Which @ngneat/effects-* package(s) are the source of the bug?

effects

Is this a regression?

No

Description

It seems that there is missed 's' in import of provideEffects function (README file).
Π‘Π½ΠΈΠΌΠΎΠΊ экрана 2023-09-11 Π² 21 32 09

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

No response

Anything else?

No response

Do you want to create a pull request?

Yes

catchError in effects not working

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

No

Description

Operator catchError in effects is not working and after throwing an error, the effect stops emitting. Please see full example in stackblitz.

public myEffect = this.createEffectFn(($: Observable<string>) =>
  $.pipe(
    tap((no) => console.log('effect invocation', no)),
    map(() => {
      throw new Error('error here');
    }),
    catchError((e) => {
      console.log('catched', e);
      return of();
    })
  )
);

This is related to #34. I think this applies to effect functions as well, see createEffectFn implementation.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/stackblitz-starters-tvg994?file=src%2Fmain.ts

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

No response

Anything else?

No response

Do you want to create a pull request?

No

some of feature

Which @ngneat/effects-* package(s) are relevant/releated to the feature request?

effects

Description

  1. export Action from @ngneat/effects not @ngneat/effects/lib/actions.types
  2. support update list with entities
  3. a simple example for reducer feature

Proposed solution

  export class ReducerEffect {
    reducerEffect = createEffect((actions: Actions) => {
      return actions.pipe(
        tap((action) => {
          switch (action.type) {
            case NsActions.ActionTypes.Add:
              return nsStore.update(addEntities(action.dataList));
           case NsActions.ActionTypes.Update:
              return fromPagesStore.update(updateEntities(action.data.id, action.data)); <---- direct to use `action.data`

export Action then can create class Action

Alternatives considered

no more

Do you want to create a pull request?

No

After upgrading angular from v13 to v15 I have a problem with catching errors in tapResult's error callback.

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

Yes

Description

I have angular upgraded from 13 to 15. I am using effects-ng for http requests.
In case I have http error I can't get the error response in tapResult's error callback and can't intercept this error inside angular error interceptor.
Is there a problem with compatibility or something else?
For example I'm getting error 404 from server but code in tapResult's error callback doesn't invokes. And the most important problem is that I can't intercept this error.
In previous version of angular (v13) everything worked fine.

  fetch$ = createEffect(actions =>
    actions.pipe(
      ofType(fetchModel),
      switchMap(({ payload }) => {
        return this.modelApiService.fetch(payload).pipe(
          tapResult(
            response => {
              this.modelRepo.addModelAndSetActive(<EntityTypeObjects.ModelEntityObject>response.data);
            },
            (error: string) => {
              //!!!can't get here!!!
              throwError(() => new Error(error));
            }
          )
        );
      })
    )
  );

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

I can't get into error callback in tapResult

Please provide the environment you discovered this bug in

Dependencies


    "@angular/animations": "^15.1.5",
    "@angular/cdk": "^15.1.5",
    "@angular/common": "^15.1.5",
    "@angular/compiler": "^15.1.5",
    "@angular/core": "^15.1.5",
    "@angular/forms": "^15.1.5",
    "@angular/platform-browser": "^15.1.5",
    "@angular/platform-browser-dynamic": "^15.1.5",
    "@angular/router": "^15.1.5",
    "rxjs": "~7.5.0",
...
    "@ngneat/effects": "^1.1.1",
    "@ngneat/effects-ng": "3.1.0",
    "@ngneat/elf": "^2.1.0",
    "@ngneat/elf-cli-ng": "^1.0.0",
    "@ngneat/elf-entities": "^4.3.1",
    "@ngneat/elf-pagination": "^1.0.1",
    "@ngneat/elf-persist-state": "^1.1.2",


### Anything else?

_No response_

### Do you want to create a pull request?

No

[Angular] actions.dispatch() fired twice

HI, i have an issue when I dispatch action, the action is dispatch twice.

Here how I declare actions and effects

actions.ts

import { actionsFactory, props } from '@ngneat/effects';
import { Downtime } from 'src/app/core/domain/models/downtime';
import { DowntimesFilter } from './downtimes.state';
import { CreateDowntimeDTO } from './models/create-downtime.dto';
import { DeleteDowntineDTO } from './models/delete-downtine-dto';
import { EditDowntimeDTO } from './models/edit-downtime.dto';
import { UpdateDowntineDTO } from './models/update-downtine-dto';

const actions = actionsFactory('downtimes');

export const setDowntimes = actions.create(
  'SET_DOWNTIMES',
  props<{ downtimes: Array<Downtime> }>()
);

export const filterDowntimes = actions.create(
  'FILTER_DOWNTIMES',
  props<{ filter: DowntimesFilter }>()
);

export const setLoading = actions.create(
  'SET_LOADING',
  props<{ loading: boolean }>()
);

export const editDowntime = actions.create(
  'EDIT_DOWNTIME',
  props<{ downtime: EditDowntimeDTO }>()
);

export const updateDowntime = actions.create(
  'UPDATE_DOWNTIME',
  props<{ downtime: UpdateDowntineDTO }>()
);

export const createDowntime = actions.create(
  'CREATE_DOWNTIME',
  props<{ downtime: CreateDowntimeDTO }>()
);

export const newDowntime = actions.create('NEW_DOWNTIME');

export const deleteDowntime = actions.create(
  'DELETE_DOWNTIME',
  props<{ downtime: DeleteDowntineDTO }>()
);

export const loadDowntimes = actions.create(
  'LOAD_DOWNTIMES',
  props<{
    client: string;
    plant: string;
    startTimestamp: number;
    endTimestamp: number;
    nodeId: string;
  }>()
);

export const loadWorkUnitsForDowntime = actions.create(
  'LOAD_WORKUNIT_FOR_DOWNTIME',
  props<{
    client: string;
    plant: string;
    nodeId: string;
  }>()
);

effects.ts

import { Injectable } from '@angular/core';
import { createEffect, ofType } from '@ngneat/effects';
import { map, switchMap, tap } from 'rxjs';
import { Downtime } from 'src/app/core/domain/models/downtime';
import { ApplicationErrorCode, applicationErrors } from '../../errors';
import { ConsoleLogger } from '../../logger/console/console-logger.service';
import { configure, debug } from '../../rxjs/operators/debug';
import { ODataApiService } from '../../services/odata-api/odata-api.service';
import { GetMachineBreakdownsBetweenTimePeriodOutput } from '../../services/odata-api/outputs/get-machine-breakdowns-between-time-period-output';
import { GetNodeAndImmediateChildrenOutput } from '../../services/odata-api/outputs/get-node-and-immediate-children-output';
import * as messagesActs from '../messages/messages.actions';
import { ERROR, NOTIFICATION, SUCCESS } from '../messages/types';
import * as acts from './downtimes.actions';
import { DowntimeRepository } from './downtimes.repository';
import { CreateDowntimeDTO } from './models/create-downtime.dto';
import { DeleteDowntineDTO } from './models/delete-downtine-dto';
import { NewDowntimeDTO } from './models/new-downtime.dto';
import { NodeDetails, NodeType } from './models/node-details';
import { UpdateDowntineDTO } from './models/update-downtine-dto';

@Injectable({
  providedIn: 'root',
})
export class DowntimesEffects {
  constructor(
    private downtimeRepo: DowntimeRepository,
    private oDataService: ODataApiService,
    private logger: ConsoleLogger
  ) {
    configure(this.logger.level, this.logger.logWithDate);
  }

  loadDowntimes$ = createEffect((actions) =>
    actions.pipe(
      ofType(acts.loadDowntimes),
      switchMap((payload) =>
        this.oDataService.getMachineBreakdownsBetweenTimePeriod({
          client: payload.client,
          plant: payload.plant,
          nodeId: payload.nodeId,
          startTimestamp: payload.startTimestamp.toString(),
          endTimestamp: payload.endTimestamp.toString(),
          plantTimezoneOffset: 0,
        })
      ),
      debug('DowntimesEffects::loadDowntimes'),
      map((res) => {
        this.downtimeRepo.setDowntimes(toDowntimes(res));
      })
    )
  );

  loadWorkUnitsForDowntime$ = createEffect(
    (actions) =>
      actions.pipe(
        ofType(acts.loadWorkUnitsForDowntime),
        // if I put a debug() here it log 4 times
        switchMap((payload) =>
          this.oDataService.getNodeAndImmediateChildren({
            client: payload.client,
            plant: payload.plant,
            nodeId: payload.nodeId,
          })
        ),
        // Log output x2
        debug('DowntimesEffects::loadWorkUnitsForDowntime'),
        tap((res) => {
          this.downtimeRepo.setWorkUnitForDowntime(toNodeDetails(res));
        })
      ),
    { dispatch: false } // <---- true doesn't change anything
  );

  newDowntime$ = createEffect(
    (actions) =>
      actions.pipe(
        ofType(acts.newDowntime),
        debug('DowntimesEffects::newDowntime'),
        map(() => this.downtimeRepo.setDowntimeDTO(new NewDowntimeDTO()))
      ),
    { dispatch: false }
  );

  createDowntime$ = createEffect(
    (actions) =>
      actions.pipe(
        ofType(acts.createDowntime),
        debug('DowntimesEffects::createDowntime'),
        switchMap((payload: { downtime: CreateDowntimeDTO }) =>
          this.oDataService.reportMultipleDowntime({
            client: payload.downtime.client,
            plant: payload.downtime.plant,
            nodeID: payload.downtime.nodeID,
            downStartEndList: [payload.downtime],
          })
        ),
        map((res) => {
          return res.d.outputCode === 0
            ? messagesActs.showMessage({
                category: SUCCESS,
                messageOrError: 'Downtime added successfuly!',
                showAs: NOTIFICATION,
              })
            : messagesActs.showMessage({
                category: ERROR,
                messageOrError:
                  applicationErrors[ApplicationErrorCode.CannotCreateDowntime],
                showAs: NOTIFICATION,
              });
        })
      ),
    { dispatch: true }
  );

  editDowntime$ = createEffect((actions) =>
    actions.pipe(
      ofType(acts.editDowntime),
      debug('DowntimesEffects::editDowntime'),
      tap((payload) => this.downtimeRepo.setDowntimeDTO(payload.downtime))
    )
  );

  updateDowntime$ = createEffect(
    (actions) =>
      actions.pipe(
        ofType(acts.updateDowntime),
        debug('DowntimesEffects::updateDowntime'),
        switchMap((payload: { downtime: UpdateDowntineDTO }) =>
          this.oDataService.updateDowntime(payload.downtime)
        ),
        map((res) => {
          return res.d.outputCode === 0
            ? messagesActs.showMessage({
                category: SUCCESS,
                messageOrError: 'Downtime updated successfuly!',
                showAs: NOTIFICATION,
              })
            : messagesActs.showMessage({
                category: ERROR,
                messageOrError:
                  applicationErrors[ApplicationErrorCode.CannotUpdateDowntime],
                showAs: NOTIFICATION,
              });
        })
      ),
    { dispatch: true }
  );

  deleteDowntime$ = createEffect(
    (actions) =>
      actions.pipe(
        ofType(acts.deleteDowntime),
        debug('DowntimesEffects::deleteDowntime'),
        switchMap((payload: { downtime: DeleteDowntineDTO }) =>
          this.oDataService.deleteDowntime(payload.downtime)
        ),
        map((res) => {
          return res.d.outputCode === 0
            ? messagesActs.showMessage({
                category: SUCCESS,
                messageOrError: 'Downtime deleted successfuly!',
                showAs: NOTIFICATION,
              })
            : messagesActs.showMessage({
                category: ERROR,
                messageOrError:
                  applicationErrors[ApplicationErrorCode.CannotDeleteDowntime],
                showAs: NOTIFICATION,
              });
        })
      ),
    { dispatch: true }
  );
}
import { Injectable, InjectionToken } from '@angular/core';
import {
  createStore,
  distinctUntilArrayItemChanged,
  propsFactory,
} from '@ngneat/elf';
import {
  selectAllEntities,
  setEntities,
  withEntities,
} from '@ngneat/elf-entities';
import { localStorageStrategy, persistState } from '@ngneat/elf-persist-state';
import { distinct, distinctUntilChanged } from 'rxjs';
import { Downtime } from 'src/app/core/domain/models/downtime';
import { DowntimesFilter } from './downtimes.state';
import { NodeDetails } from './models/node-details';
import { DowntimeDTOS } from './types';

const { withFilter, selectFilter, setFilter } = propsFactory('filter', {
  initialValue: DowntimesFilter.ALL_DOWNTIMES,
});

const { withLoading, setLoading, selectLoading } = propsFactory('loading', {
  initialValue: false,
});

const { withDowntimeDto, setDowntimeDto, selectDowntimeDto, resetDowntimeDto } =
  propsFactory('downtimeDto', {
    initialValue: {} as DowntimeDTOS,
  });

const {
  withWorkUnitForDowntime,
  setWorkUnitForDowntime,
  selectWorkUnitForDowntime,
} = propsFactory('workUnitForDowntime', {
  initialValue: [] as NodeDetails[],
});

const store = createStore(
  {
    name: 'downtimes',
  },
  withEntities<Downtime, 'downId'>({ idKey: 'downId' }),
  withWorkUnitForDowntime(),
  withFilter(DowntimesFilter.ALL_DOWNTIMES),
  withLoading(false),
  withDowntimeDto(undefined)
);

export const persist = persistState(store, {
  key: 'downtimes',
  storage: localStorageStrategy,
});

export const DOWNTIMES_REPOSITORY = new InjectionToken<DowntimeRepository>(
  'DOWNTIMES_REPOSITORY'
);

@Injectable({ providedIn: 'root' })
export class DowntimeRepository {
  filter$ = store.pipe(selectFilter()).pipe(distinctUntilChanged());
  downtimes$ = store.pipe(selectAllEntities());

  loading$ = store.pipe(selectLoading()).pipe(distinctUntilChanged());
  workUnitsForDowntime$ = store
    .pipe(selectWorkUnitForDowntime())
    .pipe(distinctUntilArrayItemChanged());

  downtimeDto$ = store.pipe(selectDowntimeDto());

  constructor() {}

  setDowntimes(downtimes: Array<Downtime>) {
    store.update(setEntities(downtimes));
  }

  setDowntimeDTO(downtime: DowntimeDTOS) {
    store.update(setDowntimeDto(downtime));
  }

  setWorkUnitForDowntime(workunits: NodeDetails[]) {
    store.update(setWorkUnitForDowntime(workunits));
  }

  setLoading(loading: boolean) {
    store.update(setLoading(loading));
  }

  reset() {
    store.update(resetDowntimeDto());
  }
}

core.module.ts

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    HttpClientModule,
    SharedModule,
    EffectsNgModule.forFeature([
      DowntimesEffects,
    ]),
  ]
})
export class CoreModule {}

core.module.ts

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    HttpClientModule,
    SharedModule,
    EffectsNgModule.forRoot([]),
  ]
})
export class AppModule {}
override ngOnInit(): void {
      this.downtimeRepository.downtimeDto$
        .pipe(takeUntil(this.onDestroy$))
        .subscribe({
          next: (downtime) => {
            // OPen a dialog (x2)
            this.addOrEditDowntimeService.createOrEditDowntime(downtime);
          },
        });
    }

I can't reproduce this behaviour with a clean slate project.

If someone have hint let me know.

PS:

  • I make sure to call actions.dispatch once
  • I try to set {dispatch: true/false } options of createEffect but no success.

OS: Mac OS 12
NPM packages:

"@ngneat/effects-ng": "^2.0.0",
"@ngneat/elf": "^1.5.6",
"@ngneat/elf-cli-ng": "^1.0.0",
"@ngneat/elf-devtools": "^1.2.1",
"@ngneat/elf-entities": "^4.3.0",
"@ngneat/elf-pagination": "^1.0.0",
"@ngneat/elf-persist-state": "^1.1.1",
"@ngneat/elf-requests": "^1.1.2",

API

interface ActionsLogger {
  log(data: { type: string } & Record<any, any>, state: Record<any, any>): void;
}

effectsConfig({
 logger: ActionsLogger,
 dispatchByDefault: boolean
})

createEffect() <== same as today ( no need for a decorator )

actions.dispatch() <==== same as today

registerEffects([Effect, Effect])

removeEffects([Effect])

Multiple effects with same class and function names

Which @ngneat/effects-* package(s) are the source of the bug?

effects, effects-ng

Is this a regression?

Yes

Description

Previously using v2 of effects-ng I could register multiple effects classes with the same name, as well as properties with the same name.

However, having recently updated to v3, and changed to using provideEffectsManager and provideEffects it seems only the first effect class is registered and all others are ignored.

Hopefully, this illustrates the issue:

// Module 1
export class DataEffects {
    readonly fetch$ = createEffect((actions) =>
        actions.pipe(
            ofType(dataActions.fetch),
            switchMap(<call to API 1>),
       ),
    );
}

// Module 2
export class DataEffects {
    readonly fetch$ = createEffect((actions) =>
        actions.pipe(
            ofType(dataActions.fetch),
            switchMap(<call to API 2>),
       ),
    );
}

//----- THIS WORKED [v2]

// Module 1
EffectsNgModule.forFeature([DataEffects])

// Module 2
EffectsNgModule.forFeature([DataEffects])

//----- THIS DOES NOT WORK [v3] (only provideEffects to be registered first works)

// Module 1
provideEffects(DataEffects)

// Module 2
provideEffects(DataEffects)

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

@angular/*: 16.0.4
@ngneat/effects-ng: 3.1.2

Anything else?

No response

Do you want to create a pull request?

No

Can't update effects-ng. Could not resolve dependency error

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

Yes

Description

I wanted to update effects-ng from v2.0.0 to the latest.
But npm install shows Could not resolve dependency error.
I found a similar error but looks like it's not fixed #45.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

npm ERR! While resolving: [email protected]
npm ERR! Found: @angular/[email protected]
npm ERR! node_modules/@angular/router
npm ERR!   @angular/router@"^15.1.5" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer @angular/router@"15.1.4" from @ngneat/[email protected]
npm ERR! node_modules/@ngneat/effects-ng
npm ERR!   @ngneat/effects-ng@"3.1.0" from the root project


### Please provide the environment you discovered this bug in

```true
"@angular/core": "^15.1.5",
    "@angular/forms": "^15.1.5",
    "@angular/platform-browser": "^15.1.5",
    "@angular/platform-browser-dynamic": "^15.1.5",
    "@angular/router": "^15.1.5",


### Anything else?

_No response_

### Do you want to create a pull request?

No

Effects execution order not guaranteed

Which @ngneat/effects-* package(s) are the source of the bug?

effects, effects-ng

Is this a regression?

No

Description

Please see the complete example at Stackblitz.

Although the effects execution order should be intuitively dependent on the execution order of actions, in reality, it depends on the effects declaration order.

The example provided in the Stackblitz prints SECOND followed by FIRST, even though it should be the other way. In order to fix this behaviour, it is necessary to change the effects declaration order (moving the load effect at the end). This can introduce some serious bugs which are hard to explore.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/stackblitz-starters-heh5qh?devToolsHeight=33&file=src%2Ftodo.effects.ts

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

No response

Anything else?

In my opinion, this should be mentioned in the docs, at least.

Do you want to create a pull request?

No

bug: {dispatch: false} in an effect is ignored in case of {dispatchByDefault: true}

Which @ngneat/effects-* package(s) are the source of the bug?

effects

Is this a regression?

No

Description

When you call the initEffects with {dispatchByDefault: true}, you no longer have the option not to dispatch an action from an effect. This is the piece of code that causes the issue:

// effects-manager.ts

if (
  effect.config?.dispatch || // <-- should be ??
  (this.config.dispatchByDefault && checkAction(maybeAction))
) {
  actions.dispatch(maybeAction);
}

I've prepared a fix and new test that covers this case, but I'd like #23 to be merged before I open a PR for this issue, if you don't mind.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

No response

Anything else?

No response

Do you want to create a pull request?

Yes

Effects directive only unregisters last effect for each effect provider

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

No

Description

Effects directive only unregisters last effect for each effect provider. The other effects keep running even after the component with the directive are destroyed.

The below uses the instance as the key, which results in each additional effect in the loop overriding the last entry.

this.sourceInstancesWithProvidersEffectsTokens.set(

During unregister sourceInstancesWithProvidersEffectsTokens is used to identify which effects to unregister. While there might be multiple providers in the set, there is always only one effect entry associated with each provider.

...this.sourceInstancesWithProvidersEffectsTokens.entries(),

Please provide a link to a minimal reproduction of the bug

This can be reproduced by adding additional effects to the effect providers in the unit tests.

Please provide the exception or error you saw

Please provide the environment you discovered this bug in

No response

Anything else?

No response

Do you want to create a pull request?

Not sure if I understand the codebase enough to ensure I don't brake the other feature "an effect will always only run once"

NullInjectorError: No provider for MatDialog

Which @ngneat/effects-* package(s) are the source of the bug?

effects

Is this a regression?

No

Description

We've been using MatDialog in our effects. However, with this package, we cannot make it work (at least using the standalone API).

When invoking actions, an exception about missing provider is thrown. Is there any way to make this work?

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/stackblitz-starters-ybh1gk

Please provide the exception or error you saw

NullInjectorError: NullInjectorError: No provider for MatDialog!
    at NullInjector.get (core.mjs:8760:27)
    at R3Injector.get (core.mjs:9189:33)
    at R3Injector.get (core.mjs:9189:33)
    at injectInjectorOnly (core.mjs:651:33)
    at Ι΅Ι΅inject (core.mjs:655:60)
    at inject (core.mjs:738:12)
    at new TodosEffects (todo.effects.ts:11:26)
    at Object.TodosEffects_Factory [as factory] (todo.effects.ts:10:26)
    at R3Injector.hydrate (core.mjs:9290:35)
    at R3Injector.get (core.mjs:9178:33)

Please provide the environment you discovered this bug in

[email protected]
β”œβ”€β”€ @angular-devkit/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @ngneat/[email protected]
β”œβ”€β”€ @ngneat/[email protected]
β”œβ”€β”€ @types/[email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
└── [email protected]

Anything else?

No response

Do you want to create a pull request?

No

No DevTools action reporting with Angular + effects-ng

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

No

Description

Following the Akita documentation of effects (https://opensource.salesforce.com/akita/docs/angular/effects) I tried to integrate this library into my project. It all went flawlessly, however the dispatched actions does not show up in the Redux DevTools, which is actually very useful.

It works however with @datorama/akita-ng-effects, the actions show up every time they're dispatched.

I have pretty much followed the README, declaring:

The actions:

export const fooAction = actionsFactory('foo');
export const loadFooDetails = fooAction.create('Load Foo Details');

Then the effects injectable:

@Injectable({ providedIn: 'root' })
export class FooEffects {
// ...
  loadFoo$ = createEffect((actions) =>
    actions.pipe(
      ofType(loadGeoPlannerDetails),
      switchMap(() => this.performFetch()),
  );
}

Later dispatched with:

this.actions.dispatch(loadGeoPlannerDetails());

Of course importing the effects module:

imports: [ 
 ...,
 EffectsNgModule.forFeature([FooEffects]) ]
]

Pretty much the most basic stuff - it works, fetch is being launched, data in store changed, but it does not show up in the dev tools. For the moment I just have to resort back to Akita's deprecated implementation as it really makes it easier to develop with dev tools.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

Angular 13, Redux Dev Tools 3.0.11

Anything else?

No response

Do you want to create a pull request?

No

Dispatched Actions from Effects getting not emitted though "customActionsStream"

Which @ngneat/effects-* package(s) are the source of the bug?

effects-ng

Is this a regression?

No

Description

During writing jest tests for effects I've encountered the problem, that actions, which are dispatched by effects are not getting emitted inside customActionsStream. During runtime and regular use (without customActionsStream), this problem does not exist.

The simple test example below illustrates the problem: loadTodos gets dispached and dispatches after completion loadTodos2.
The test fails, because loadTodos2 never apprears inside customActionsStream$.

How I've encountered this problem? / Use Case
I want to test effects, which are performing store updates. To ensure, that the update process triggered by the effect is properly performed, the effect dispatches a "completion" action. When the completion action is dispatched, I'm able to verify, that the performed state mutations/ async side-effects are properly done.

Reproduction example:

import { TestBed } from '@angular/core/testing';
import { Actions, createAction, createEffect, ofType } from '@ngneat/effects';
import { provideEffects, provideEffectsManager } from '@ngneat/effects-ng';
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core';

const loadTodos = createAction('[Todos] Load Todos');
const loadTodos2 = createAction('[Todos] Load Todos2');

@Injectable()
class EffectsOne {
  loadTodos$ = createEffect(
    (actions$) =>
      actions$.pipe(
        ofType(loadTodos),
        map(() => loadTodos2()),
      ),
    { dispatch: true },
  );
}

it('should emit loadTodos2 action', (done) => {
  const customActionsStream$ = new Actions();

  TestBed.configureTestingModule({
    providers: [
      provideEffects(EffectsOne),
      // eslint-disable-next-line rxjs/finnish
      provideEffectsManager({ customActionsStream: customActionsStream$, dispatchByDefault: true }),
    ],
  });

  customActionsStream$.pipe(ofType(loadTodos2)).subscribe((todos) => {
    expect(todos).toBeDefined();
    done();
  });
  customActionsStream$.dispatch(loadTodos());
});

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

Error: thrown: "Exceeded timeout of 5000 ms for a test while waiting for `done()` to be called.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

Please provide the environment you discovered this bug in

jest 29.5.0
@ngneat/effects 2.1.1
@ngneat/effects-ng 3.1.2

Anything else?

No response

Do you want to create a pull request?

No

Depdendency Update (angular 16)

Which @ngneat/effects-* package(s) are relevant/releated to the feature request?

effects-ng

Description

To ensure compatiblity with angular 16 projects, the dependencies of ng-effects needed to be updated.

Proposed solution

Migrate nx workspace via nx migrate.

Alternatives considered

None, because of no practical alternatives to depedency update.

Do you want to create a pull request?

Yes

Parameters not assignable to parameter of type

Which @ngneat/effects-* package(s) are the source of the bug?

effects

Is this a regression?

No

Description

I'm trying to put ngneat/effects on my ionic project but I get an error I don't understand, when I put the 'ofType' operator to observe my action it triggers this error:

Argument of type '(source: Observable<Action<string>>) => Observable<{ type: "[Spending] Load Spendings"; }>' is not assignable to parameter of type 'OperatorFunction<Action, { type: "[Spending] Load Spendings"; }>'.

it tells me it's a ts error, so I try to change the node version but nothing I still get the same error.

My action:

import { actionsFactory } from "@ngneat/effects";

const spendingActions = actionsFactory('Spending');

const loadSpending = spendingActions.create('[Spending] Load Spendings');

export { loadSpending };

my effect:

loadingSpendings$ = createEffect((actions) => 
        actions.pipe(
            ofType(spendingActions.loadSpending), // error triggered here
            mergeMap(() => 
                this.spendingService.loadSpending()
            ),
            tap(setSpendings),
        )
    )

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in

Node version: 18.10
Ts version: 4.8.4

Anything else?

No response

Do you want to create a pull request?

No

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.