-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(create-effect): add createEffect
closes #27
- Loading branch information
Showing
23 changed files
with
312 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
--- | ||
title: createEffect | ||
description: ngxtension/create-effect | ||
--- | ||
|
||
`createEffect` is a standalone version of [NgRx ComponentStore Effect](https://ngrx.io/guide/component-store/effect) | ||
|
||
:::tip[From ComponentStore documentation] | ||
|
||
- Effects isolate side effects from components, allowing for more pure components that select state and trigger updates and/or effects in ComponentStore(s). | ||
- Effects are Observables listening for the inputs and piping them through the "prescription". | ||
- Those inputs can either be values or Observables of values. | ||
- Effects perform tasks, which are synchronous or asynchronous. | ||
|
||
::: | ||
|
||
In short, `createEffect` creates a callable function that accepts some data (imperative) or some stream of data (declarative), or none at all. | ||
|
||
```ts | ||
import { createEffect } from 'ngxtension/create-effect'; | ||
``` | ||
|
||
## Usage | ||
|
||
```ts | ||
@Component({}) | ||
export class Some { | ||
log = createEffect<number>( | ||
pipe( | ||
map((value) => value * 2), | ||
tap(console.log.bind(console, 'double is -->')) | ||
) | ||
); | ||
|
||
ngOnInit() { | ||
// start the effect | ||
this.log(interval(1000)); | ||
} | ||
} | ||
``` | ||
|
||
### Injection Context | ||
|
||
`createEffect` accepts an optional `Injector` so we can call `createEffect` outside of an Injection Context. | ||
|
||
```ts | ||
@Component({}) | ||
export class Some { | ||
// 1. setup an Input; we know that Input isn't resolved in constructor | ||
@Input() multiplier = 2; | ||
|
||
// 2. grab the Injector | ||
private injector = inject(Injector); | ||
|
||
ngOnInit() { | ||
// 3. create log effect in ngOnInit; where Input is resolved | ||
const log = createEffect<number>( | ||
pipe( | ||
map((value) => value * this.multiplier), | ||
tap(console.log.bind(console, 'multiply is -->')) | ||
), | ||
// 4. pass in the injector | ||
this.injector | ||
); | ||
|
||
// 5. start the effect | ||
log(interval(1000)); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
libs/local-plugin/src/generators/entry-point/generator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
describe('entry-point generator', () => { | ||
// let tree: Tree; | ||
// | ||
// beforeEach(() => { | ||
// tree = createTreeWithEmptyWorkspace(); | ||
// }); | ||
|
||
it('should run successfully', async () => { | ||
expect(true).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { librarySecondaryEntryPointGenerator } from '@nx/angular/generators'; | ||
import type { GeneratorOptions as SecondaryEntryPointGeneratorOptions } from '@nx/angular/src/generators/library-secondary-entry-point/schema'; | ||
import { formatFiles, Tree } from '@nx/devkit'; | ||
import convertEntryPointToProjectGenerator from '../convert-entry-point-to-project/generator'; | ||
|
||
export async function entryPointGenerator( | ||
tree: Tree, | ||
options: SecondaryEntryPointGeneratorOptions | ||
) { | ||
await librarySecondaryEntryPointGenerator(tree, options); | ||
await convertEntryPointToProjectGenerator(tree, { | ||
name: options.name, | ||
project: options.library, | ||
}); | ||
await formatFiles(tree); | ||
} | ||
|
||
export default entryPointGenerator; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"$id": "LocalPluginEntryPoint", | ||
"title": "Creates a secondary entry point for a library", | ||
"description": "Creates a secondary entry point for an Angular publishable library.", | ||
"type": "object", | ||
"cli": "nx", | ||
"properties": { | ||
"name": { | ||
"type": "string", | ||
"description": "The name of the secondary entry point.", | ||
"$default": { | ||
"$source": "argv", | ||
"index": 0 | ||
}, | ||
"x-prompt": "What name would you like to use for the secondary entry point?", | ||
"pattern": "^[a-zA-Z].*$", | ||
"x-priority": "important" | ||
}, | ||
"library": { | ||
"type": "string", | ||
"description": "The name of the library to create the secondary entry point for.", | ||
"x-prompt": "What library would you like to create the secondary entry point for?", | ||
"pattern": "^[a-zA-Z].*$", | ||
"x-dropdown": "projects", | ||
"x-priority": "important" | ||
}, | ||
"skipModule": { | ||
"type": "boolean", | ||
"description": "Skip generating a module for the secondary entry point.", | ||
"default": false | ||
} | ||
}, | ||
"additionalProperties": false, | ||
"required": ["name", "library"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as convertEntryPointToProjectGenerator } from './generators/convert-entry-point-to-project/generator'; | ||
export { default as entryPointGenerator } from './generators/entry-point/generator'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# ngxtension/create-effect | ||
|
||
Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/create-effect`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"lib": { | ||
"entryFile": "src/index.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "ngxtension/create-effect", | ||
"$schema": "../../../node_modules/nx/schemas/project-schema.json", | ||
"projectType": "library", | ||
"sourceRoot": "libs/ngxtension/create-effect/src", | ||
"targets": { | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "libs/ngxtension/jest.config.ts", | ||
"testPathPattern": ["create-effect"], | ||
"passWithNoTests": true | ||
}, | ||
"configurations": { | ||
"ci": { | ||
"ci": true, | ||
"codeCoverage": true | ||
} | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/linter:eslint", | ||
"outputs": ["{options.outputFile}"], | ||
"options": { | ||
"lintFilePatterns": [ | ||
"libs/ngxtension/create-effect/**/*.ts", | ||
"libs/ngxtension/create-effect/**/*.html" | ||
] | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Component } from '@angular/core'; | ||
import { TestBed, fakeAsync, tick } from '@angular/core/testing'; | ||
import { interval, tap } from 'rxjs'; | ||
import { createEffect } from './create-effect'; | ||
|
||
describe(createEffect.name, () => { | ||
@Component({ | ||
standalone: true, | ||
template: '', | ||
}) | ||
class Foo { | ||
count = 0; | ||
log = createEffect<number>(tap(() => (this.count += 1))); | ||
|
||
ngOnInit() { | ||
this.log(interval(1000)); | ||
} | ||
} | ||
|
||
it('should run until component is destroyed', fakeAsync(() => { | ||
const fixture = TestBed.createComponent(Foo); | ||
const component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
expect(component.count).toEqual(0); | ||
|
||
tick(1000); | ||
expect(component.count).toEqual(1); | ||
|
||
tick(1000); | ||
expect(component.count).toEqual(2); | ||
|
||
fixture.destroy(); | ||
tick(1000); | ||
expect(component.count).toEqual(2); | ||
})); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { | ||
DestroyRef, | ||
Injector, | ||
inject, | ||
runInInjectionContext, | ||
} from '@angular/core'; | ||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; | ||
import { assertInjector } from 'ngxtension/assert-injector'; | ||
import { | ||
Observable, | ||
Subject, | ||
Subscription, | ||
isObservable, | ||
of, | ||
retry, | ||
} from 'rxjs'; | ||
|
||
/** | ||
* This code is a copied `ComponentStore.effect()` method from NgRx and edited to: | ||
* 1) be a standalone function; | ||
* 2) use `takeUntilDestroyed()` with an injected `DestroyRef`; | ||
* 3) resubscribe on errors. | ||
* | ||
* Credits: NgRx Team | ||
* https://ngrx.io/ | ||
* Source: https://github.com/ngrx/platform/blob/main/modules/component-store/src/component-store.ts#L382 | ||
* Docs: | ||
* https://ngrx.io/guide/component-store/effect#effect-method | ||
*/ | ||
export function createEffect< | ||
ProvidedType = void, | ||
OriginType extends | ||
| Observable<ProvidedType> | ||
| unknown = Observable<ProvidedType>, | ||
ObservableType = OriginType extends Observable<infer A> ? A : never, | ||
ReturnType = ProvidedType | ObservableType extends void | ||
? ( | ||
observableOrValue?: ObservableType | Observable<ObservableType> | ||
) => Subscription | ||
: ( | ||
observableOrValue: ObservableType | Observable<ObservableType> | ||
) => Subscription | ||
>( | ||
generator: (origin$: OriginType) => Observable<unknown>, | ||
injector?: Injector | ||
): ReturnType { | ||
injector = assertInjector(createEffect, injector); | ||
return runInInjectionContext(injector, () => { | ||
const destroyRef = inject(DestroyRef); | ||
const origin$ = new Subject<ObservableType>(); | ||
generator(origin$ as OriginType) | ||
.pipe(retry(), takeUntilDestroyed(destroyRef)) | ||
.subscribe(); | ||
|
||
return (( | ||
observableOrValue?: ObservableType | Observable<ObservableType> | ||
): Subscription => { | ||
const observable$ = isObservable(observableOrValue) | ||
? observableOrValue.pipe(retry()) | ||
: of(observableOrValue); | ||
return observable$ | ||
.pipe(takeUntilDestroyed(destroyRef)) | ||
.subscribe((value) => { | ||
origin$.next(value as ObservableType); | ||
}); | ||
}) as unknown as ReturnType; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './create-effect'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.