Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create ngrx-accelerator #399

Merged
merged 10 commits into from
Aug 26, 2024
Merged
40 changes: 40 additions & 0 deletions libs/ngrx-accelerator/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "ocx",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "ocx",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}
7 changes: 7 additions & 0 deletions libs/ngrx-accelerator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ngrx-accelerator

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test ngrx-accelerator` to execute the unit tests.
22 changes: 22 additions & 0 deletions libs/ngrx-accelerator/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'ngrx-accelerator',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../coverage/libs/ngrx-accelerator',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
}
10 changes: 10 additions & 0 deletions libs/ngrx-accelerator/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/ngrx-accelerator",
"lib": {
"entryFile": "src/index.ts"
},
"assets": [
"CHANGELOG.md"
]
}
22 changes: 22 additions & 0 deletions libs/ngrx-accelerator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@onecx/ngrx-accelerator",
"version": "5.3.2",
"peerDependencies": {
"@angular/core": "^18.0.5",
"@angular/router": "^18.0.5",
"deepmerge": "^4.3.1",
"fast-deep-equal": "^3.1.3",
"@ngrx/effects": "^18.0.1",
"@ngrx/operators": "^18.0.1",
"@ngrx/router-store": "^18.0.1",
"@ngrx/store": "^18.0.1",
"rxjs": "7.8.1",
"tslib": "^2.6.3",
"zod": "^3.23.8"
},
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"publishConfig": {
"access": "public"
}
}
47 changes: 47 additions & 0 deletions libs/ngrx-accelerator/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "ngrx-accelerator",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/ngrx-accelerator/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/ngrx-accelerator",
"main": "libs/ngrx-accelerator/src/index.ts",
"tsConfig": "libs/ngrx-accelerator/tsconfig.lib.json",
"assets": ["libs/ngrx-accelerator/*.md"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/ngrx-accelerator/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/ngrx-accelerator/**/*.ts", "libs/ngrx-accelerator/package.json"]
}
},
"release": {
"executor": "nx-release:build-update-publish",
"options": {
"libName": "ngrx-accelerator"
}
}
}
}
11 changes: 11 additions & 0 deletions libs/ngrx-accelerator/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Effects
export * from './lib/utils/effects/create-query-params-effect'
export * from './lib/utils/effects/filter-for-navigated-to'
export * from './lib/utils/effects/filter-for-only-query-params-changed'
export * from './lib/utils/effects/filter-for-query-params-changed'

// Selectors
export * from './lib/utils/selectors/create-child-selectors'

// Local Storage
export * from './lib/utils/local-storage/lazy-loading-merge-reducer'
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ActivatedRoute, Router } from '@angular/router'
import { Actions, createEffect, ofType } from '@ngrx/effects'
import { concatLatestFrom } from '@ngrx/operators'
import { ActionCreator, Creator } from '@ngrx/store'
import { tap } from 'rxjs'

export function createQueryParamsEffect<AC extends ActionCreator<string, Creator>>(
actions$: Actions,
actionType: AC,
router: Router,
activatedRoute: ActivatedRoute,
reducer: (state: Record<string, any>, action: ReturnType<AC>) => Record<string, any>
) {
return createEffect(
() => {
return actions$.pipe(
ofType(actionType),
concatLatestFrom(() => activatedRoute.queryParams),
tap(([action, queryParams]) => {
const params = reducer(queryParams, action)
router.navigate([], {
relativeTo: activatedRoute,
queryParams: params,
replaceUrl: true,
onSameUrlNavigation: 'reload',
})
})
)
},
{ dispatch: false }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { filter, MonoTypeOperatorFunction } from 'rxjs'
import { ActivatedRoute, Router } from '@angular/router'
import { Type } from '@angular/core'
import { RouterNavigatedAction } from '@ngrx/router-store'

export function filterForNavigatedTo<A extends RouterNavigatedAction>(
router: Router,
component: Type<any>
): MonoTypeOperatorFunction<A> {
return (source) => {
return source.pipe(
filter(() => {
return checkForComponent(component, router.routerState.root)
})
)
}
}

function checkForComponent(component: any, route: ActivatedRoute): boolean {
if (route.component === component) {
return true
}
for (const c of route.children) {
const r = checkForComponent(component, c)
if (r) {
return true
}
}
return false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Router, RoutesRecognized } from '@angular/router'
import { RouterNavigatedAction } from '@ngrx/router-store'
import { filter, map, MonoTypeOperatorFunction, withLatestFrom } from 'rxjs'

/**
* @deprecated use filterOutOnlyQueryParamsChanged
*/
export function filterForOnlyQueryParamsChanged<A extends RouterNavigatedAction>(
router: Router
): MonoTypeOperatorFunction<A> {
return filterOutOnlyQueryParamsChanged(router)
}

export function filterOutOnlyQueryParamsChanged<A extends RouterNavigatedAction>(
router: Router
): MonoTypeOperatorFunction<A> {
return (source) => {
return source.pipe(
withLatestFrom(
router.events.pipe(
filter((e) => e instanceof RoutesRecognized),
map(() => router.routerState)
)
),
filter(([action, previousRouterState]) => {
const previousPath = previousRouterState.snapshot.url.split('?')[0]
const currentPath = action.payload.event.urlAfterRedirects.split('?')[0]

return previousPath !== currentPath
}),
map(([action]) => action)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { RouterNavigatedAction } from '@ngrx/router-store'
import { ZodType } from 'zod'
import { MonoTypeOperatorFunction, filter, withLatestFrom, map } from 'rxjs'
import equal from 'fast-deep-equal'
import { Router, RoutesRecognized } from '@angular/router'

/**
* @deprecated use filterOutQueryParamsHaveNotChanged
*/
export function filterForQueryParamsChanged<A extends RouterNavigatedAction>(
router: Router,
queryParamsTypeDef: ZodType,
allowEmptyQueryParamsList = false
): MonoTypeOperatorFunction<A> {
return filterOutQueryParamsHaveNotChanged(router, queryParamsTypeDef, allowEmptyQueryParamsList)
}

export function filterOutQueryParamsHaveNotChanged<A extends RouterNavigatedAction>(
router: Router,
queryParamsTypeDef: ZodType,
allowEmptyQueryParamsList = false
): MonoTypeOperatorFunction<A> {
return (source) => {
return source.pipe(
withLatestFrom(
router.events.pipe(
filter((e) => e instanceof RoutesRecognized),
map(() => router.routerState)
)
),
filter(([action, previousRouterState]) => {
if (
!allowEmptyQueryParamsList &&
Object.keys(action?.payload?.routerState?.root?.queryParams || {}).length === 0
) {
return false
}
const currentQueryParams = previousRouterState.snapshot.root.queryParams
const actionResult = queryParamsTypeDef.safeParse(action?.payload?.routerState?.root?.queryParams)
const currentResult = queryParamsTypeDef.safeParse(currentQueryParams)

if (actionResult.success && currentResult.success) {
const actionParams = actionResult.data
const currentParams = currentResult.data
if (
allowEmptyQueryParamsList &&
Object.keys(actionParams).length === 0 &&
Object.keys(currentParams).length === 0
) {
return true
}
return !equal(actionParams, currentParams)
}
return false
}),
map(([action]) => action)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import deepmerge from 'deepmerge'

export const lazyLoadingMergeReducer = (state: any, rehydratedState: any, _action: any) => {
const overwriteMerge = (_destinationArray: any, sourceArray: any, _options: any) => sourceArray
const options: deepmerge.Options = {
arrayMerge: overwriteMerge,
}
const keysToRehydrate = Object.keys(rehydratedState).filter((key) => state[key])
if (keysToRehydrate.length) {
const stateToRehydrate = Object.keys(rehydratedState).reduce((acc: Record<string, unknown>, key) => {
if (keysToRehydrate.includes(key)) {
acc[key] = rehydratedState[key]
}
return acc
}, {})
state = deepmerge(state, stateToRehydrate, options)
keysToRehydrate.forEach((key) => {
delete rehydratedState[key]
})
}
return state
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { MemoizedSelector, createSelector } from '@ngrx/store'

type Primitive = string | number | bigint | boolean | null | undefined

type ChildSelectors<State extends Record<string, any>, ChildState> = ChildState extends Primitive | unknown[] | Date
? Record<string, never>
: {
[K in keyof ChildState & string as `select${Capitalize<K>}`]: MemoizedSelector<State, ChildState[K]>
}

function capitalize<T extends string>(text: T): Capitalize<T> {
return (text.charAt(0).toUpperCase() + text.substring(1)) as Capitalize<T>
}

export function createChildSelectors<State extends Record<string, any>, ChildState extends Record<string, any>>(
featureSelector: MemoizedSelector<State, ChildState>,
initialChildState: ChildState
): ChildSelectors<State, ChildState> {
return Object.keys(initialChildState).reduce(
(nestedSelectors, nestedKey) => ({
...nestedSelectors,
[`select${capitalize(nestedKey)}`]: createSelector(featureSelector, (parentState) => parentState?.[nestedKey]),
}),
{} as ChildSelectors<State, ChildState>
)
}
8 changes: 8 additions & 0 deletions libs/ngrx-accelerator/src/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment
globalThis.ngJest = {
testEnvironmentOptions: {
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
},
}
import 'jest-preset-angular/setup-jest'
Loading
Loading