Skip to content

Commit

Permalink
feat: create ngrx-accelerator (#399)
Browse files Browse the repository at this point in the history
* feat: initialize new lib

* feat: add ngrx utils to lib and deprecate ngrx utils in pia

* fix: linter error

* fix: add missing dependencies

* fix: fix typing issues

* feat: add navigationMergeReducer

* fix: add missing dependency

* fix: fix linting

* feat: add lazyLoadingMergeReducer to ngrx-accelerator

* fix: fix linting issue
  • Loading branch information
bastianjakobi authored Aug 26, 2024
1 parent 023dd8b commit d3007a1
Show file tree
Hide file tree
Showing 26 changed files with 463 additions and 36 deletions.
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

0 comments on commit d3007a1

Please sign in to comment.