Skip to content

Commit

Permalink
feat: added inject params and inject query params
Browse files Browse the repository at this point in the history
  • Loading branch information
eneajaho committed Nov 15, 2023
2 parents 64be3c1 + a3d1a60 commit acec062
Show file tree
Hide file tree
Showing 15 changed files with 403 additions and 0 deletions.
57 changes: 57 additions & 0 deletions docs/src/content/docs/utilities/Injectors/inject-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: injectParams
description: ngxtension/inject-params
---

`injectParams` is a helper function that allows us to inject params from the current route as a signal.

Having params as a signal helps in a modern angular signals based architecture.

```ts
import { injectParams } from 'ngxtension/inject-params';
```

## Usage

`injectParams` when is called, returns a signal with the current route params.

```ts
@Component({
standalone: true,
template: '<div>{{params() | json}}</div>',
})
class TestComponent {
params = injectParams();
}
```

If we want to get the value for a specific param, we can pass the name of the param to `injectParams`.

```ts
@Component({
template: `
@if (user()) {
<div>{{ user.name }}</div>
} @else {
<div>No user!</div>
}
`,
})
class TestComponent {
userId = injectParams('id'); // returns a signal with the value of the id param

user = computedFrom(
[this.userId],
switchMap((id) => this.userService.getUser(id).pipe(startWith(null)))
);
}
```

Or, if we want to transform the params, we can pass a function to `injectParams`.

```ts
@Component()
class TestComponent {
paramsKeys = injectParams((params) => Object.keys(params)); // returns a signal with the keys of the params
}
```
64 changes: 64 additions & 0 deletions docs/src/content/docs/utilities/Injectors/inject-query-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: injectQueryParams
description: ngxtension/inject-query-params
---

`injectQueryParams` is a helper function that allows us to inject query params from the current route as a signal.

Having query params as a signal helps in a modern angular signals based architecture.

```ts
import { injectQueryParams } from 'ngxtension/inject-query-params';
```

## Usage

`injectQueryParams` when is called, returns a signal with the current query params.

```ts
@Component({
standalone: true,
template: '<div>{{queryParams() | json}}</div>',
})
class TestComponent {
queryParams = injectQueryParams();
}
```

If we want to get the value for a specific query param, we can pass the name of the query param to `injectQueryParams`.

```ts
@Component({
template: `
Search results for: {{ searchParam() }}
@for (user of filteredUsers()) {
<div>{{ user.name }}</div>
} @empty {
<div>No users!</div>
}
`,
})
class TestComponent {
searchParam = injectQueryParams('search'); // returns a signal with the value of the search query param

filteredUsers = computedFrom(
[this.searchParam],
switchMap((searchQuery) => this.userService.getUsers(searchQuery).pipe(startWith([])))
);
}
```

Or, if we want to transform the query params, we can pass a function to `injectQueryParams`.

```ts
@Component()
class TestComponent {
queryParamsKeys = injectQueryParams((params) => Object.keys(params)); // returns a signal with the keys of the query params

allQueryParamsArePassed = computed(() => {
const keys = this.queryParamsKeys();
return ['search', 'sort', 'page'].every((x) => keys.includes(x));
});
}
```
3 changes: 3 additions & 0 deletions libs/ngxtension/inject-params/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ngxtension/inject-params

Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/inject-params`.
5 changes: 5 additions & 0 deletions libs/ngxtension/inject-params/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "src/index.ts"
}
}
33 changes: 33 additions & 0 deletions libs/ngxtension/inject-params/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "ngxtension/inject-params",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "libs/ngxtension/inject-params/src",
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/ngxtension/jest.config.ts",
"testPathPattern": ["inject-params"],
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": [
"libs/ngxtension/inject-params/**/*.ts",
"libs/ngxtension/inject-params/**/*.html"
]
}
}
}
}
1 change: 1 addition & 0 deletions libs/ngxtension/inject-params/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './inject-params';
37 changes: 37 additions & 0 deletions libs/ngxtension/inject-params/src/inject-params.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';

import { provideRouter } from '@angular/router';
import { RouterTestingHarness } from '@angular/router/testing';
import { injectParams } from './inject-params';

describe(injectParams.name, () => {
it('returns a signal everytime the route params change based on the param passed to the fn', async () => {
TestBed.configureTestingModule({
providers: [
provideRouter([{ path: 'user/:id', component: UserProfileComponent }]),
],
});

const harness = await RouterTestingHarness.create();

const instance = await harness.navigateByUrl(
'/user/angular',
UserProfileComponent
);

expect(instance.params()).toEqual({ id: 'angular' });
expect(instance.userId()).toEqual('angular');
expect(instance.paramKeysList()).toEqual(['id']);
});
});

@Component({
standalone: true,
template: ``,
})
export class UserProfileComponent {
params = injectParams();
userId = injectParams('id');
paramKeysList = injectParams((params) => Object.keys(params));
}
55 changes: 55 additions & 0 deletions libs/ngxtension/inject-params/src/inject-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { assertInInjectionContext, inject, type Signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute, type Params } from '@angular/router';
import { map } from 'rxjs';

/**
* Injects the params from the current route.
*/
export function injectParams(): Signal<Params>;

/**
* Injects the params from the current route and returns the value of the provided key.
* @param key
*/
export function injectParams(key: string): Signal<string | null>;

/**
* Injects the params from the current route and returns the result of the provided transform function.
* @param transform
*/
export function injectParams<T>(transform: (params: Params) => T): Signal<T>;

/**
* Injects the params from the current route.
* If a key is provided, it will return the value of that key.
* If a transform function is provided, it will return the result of that function.
* Otherwise, it will return the entire params object.
*
* @example
* const userId = injectParams('id'); // returns the value of the 'id' param
* const userId = injectParams(p => p['id'] as string); // same as above but can be used with a custom transform function
* const params = injectParams(); // returns the entire params object
*
* @param keyOrTransform OPTIONAL The key of the param to return, or a transform function to apply to the params object
*/
export function injectParams<T>(
keyOrTransform?: string | ((params: Params) => T)
): Signal<T | Params | string | null> {
assertInInjectionContext(injectParams);
const route = inject(ActivatedRoute);
const params = route.snapshot.params || {};

if (typeof keyOrTransform === 'function') {
return toSignal(route.params.pipe(map(keyOrTransform)), {
initialValue: keyOrTransform(params),
});
}

const getParam = (params: Params) =>
keyOrTransform ? params?.[keyOrTransform] ?? null : params;

return toSignal(route.params.pipe(map(getParam)), {
initialValue: getParam(params),
});
}
3 changes: 3 additions & 0 deletions libs/ngxtension/inject-query-params/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ngxtension/inject-query-params

Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/inject-query-params`.
5 changes: 5 additions & 0 deletions libs/ngxtension/inject-query-params/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "src/index.ts"
}
}
33 changes: 33 additions & 0 deletions libs/ngxtension/inject-query-params/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "ngxtension/inject-query-params",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "libs/ngxtension/inject-query-params/src",
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/ngxtension/jest.config.ts",
"testPathPattern": ["inject-query-params"],
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": [
"libs/ngxtension/inject-query-params/**/*.ts",
"libs/ngxtension/inject-query-params/**/*.html"
]
}
}
}
}
1 change: 1 addition & 0 deletions libs/ngxtension/inject-query-params/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './inject-query-params';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';

import { provideRouter } from '@angular/router';
import { RouterTestingHarness } from '@angular/router/testing';
import { injectQueryParams } from './inject-query-params';

describe(injectQueryParams.name, () => {
it('returns a signal everytime the query params change based on the param passed to the fn', async () => {
TestBed.configureTestingModule({
providers: [
provideRouter([{ path: 'search', component: SearchComponent }]),
],
});

const harness = await RouterTestingHarness.create();

const instance = await harness.navigateByUrl(
'/search?query=Angular',
SearchComponent
);

expect(instance.queryParams()).toEqual({ query: 'Angular' });
expect(instance.searchParam()).toEqual('Angular');
expect(instance.paramKeysList()).toEqual(['query']);

await harness.navigateByUrl('/search?query=IsCool!&id=2');

expect(instance.queryParams()).toEqual({ query: 'IsCool!', id: '2' });
expect(instance.searchParam()).toEqual('IsCool!');
expect(instance.paramKeysList()).toEqual(['query', 'id']);
});
});

@Component({
standalone: true,
template: ``,
})
export class SearchComponent {
queryParams = injectQueryParams();
searchParam = injectQueryParams('query');
paramKeysList = injectQueryParams((params) => Object.keys(params));
}
Loading

0 comments on commit acec062

Please sign in to comment.