Skip to content

Commit

Permalink
feat: added computedPrevious helper fn (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
eneajaho authored Nov 30, 2023
1 parent 407f5bd commit 96d5de4
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 0 deletions.
36 changes: 36 additions & 0 deletions docs/src/content/docs/utilities/Signals/computed-previous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: computedPrevious
description: ngxtension/computed-previous
badge: stable
contributor: enea-jahollari
---

`computedPrevious` is a helper function that returns a `signal` that emits the previous value of the passed `signal`. It's useful when you want to keep track of the previous value of a signal.
It will emit `null` as the previous value when the signal is first created.

```ts
import { computedPrevious } from 'ngxtension/computed-previous';
```

## Usage

`computedPrevious` accepts a `Signal` and returns a `Signal` that emits the previous value of the passed `Signal`.

```ts
const a = signal(1);
const b = computedPrevious(a);

console.log(b()); // null

a.set(2);
console.log(b()); // 1

a.set(3);
console.log(b()); // 2
```

## API

```ts
computedPrevious<T>(signal: Signal<T>): Signal<T | null>
```
3 changes: 3 additions & 0 deletions libs/ngxtension/computed-previous/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ngxtension/computed-previous

Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/computed-previous`.
5 changes: 5 additions & 0 deletions libs/ngxtension/computed-previous/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/computed-previous/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "ngxtension/computed-previous",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "libs/ngxtension/computed-previous/src",
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/ngxtension/jest.config.ts",
"testPathPattern": ["computed-previous"],
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": [
"libs/ngxtension/computed-previous/**/*.ts",
"libs/ngxtension/computed-previous/**/*.html"
]
}
}
}
}
44 changes: 44 additions & 0 deletions libs/ngxtension/computed-previous/src/computed-previous.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component, signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { computedPrevious } from './computed-previous';

describe(computedPrevious.name, () => {
@Component({ standalone: true, template: '{{previous()}}' })
class Test {
value = signal(0);
previous = computedPrevious(this.value);
}

function setup() {
const fixture = TestBed.createComponent(Test);
fixture.detectChanges();
return fixture.componentInstance;
}

it('should work properly', () => {
const cmp = setup();

expect(cmp.value()).toEqual(0);
expect(cmp.previous()).toEqual(null);

cmp.value.set(1);

expect(cmp.value()).toEqual(1);
expect(cmp.previous()).toEqual(0);

cmp.value.set(2);

expect(cmp.value()).toEqual(2);
expect(cmp.previous()).toEqual(1);

cmp.value.set(2);

expect(cmp.value()).toEqual(2);
expect(cmp.previous()).toEqual(1);

cmp.value.set(3);

expect(cmp.value()).toEqual(3);
expect(cmp.previous()).toEqual(2);
});
});
43 changes: 43 additions & 0 deletions libs/ngxtension/computed-previous/src/computed-previous.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { computed, type Signal } from '@angular/core';

/**
* Returns a signal that emits the previous value of the given signal.
* The first time the signal is emitted, the previous value will be `null`.
*
* @example
* ```ts
* const value = signal(0);
* const previous = computedPrevious(value);
*
* effect(() => {
* console.log('Current value:', value());
* console.log('Previous value:', previous());
* });
*
* Logs:
* // Current value: 0
* // Previous value: null
*
* value.set(1);
*
* Logs:
* // Current value: 1
* // Previous value: 0
*```
* @param s Signal to compute previous value for
* @returns Signal that emits previous value of `s`
*/
export function computedPrevious<T>(s: Signal<T>): Signal<T | null> {
let current = null as T;
let previous = null as T;

return computed(() => {
const value = s();
if (value !== current) {
previous = current;
current = value;
}
return previous;
});
}
1 change: 1 addition & 0 deletions libs/ngxtension/computed-previous/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './computed-previous';
3 changes: 3 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"ngxtension/computed-from": [
"libs/ngxtension/computed-from/src/index.ts"
],
"ngxtension/computed-previous": [
"libs/ngxtension/computed-previous/src/index.ts"
],
"ngxtension/connect": ["libs/ngxtension/connect/src/index.ts"],
"ngxtension/create-effect": [
"libs/ngxtension/create-effect/src/index.ts"
Expand Down

0 comments on commit 96d5de4

Please sign in to comment.