Skip to content

Commit

Permalink
feat(computed): add computed/extendedComputed with access to previous…
Browse files Browse the repository at this point in the history
… computed value
  • Loading branch information
nartc committed Oct 29, 2023
1 parent 00fb9c7 commit 7260727
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 0 deletions.
3 changes: 3 additions & 0 deletions libs/ngxtension/computed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ngxtension/computed

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

describe(computed.name, () => {
@Component({
standalone: true,
template: '',
})
class Test {
count = signal(0);
multiplier = signal(1);

result = computed<number>((currentValue) => {
if (this.multiplier() % 2 === 0) {
return this.count() * this.multiplier();
}
return currentValue;
});

track = 0;
trackedResult = computed<number>((currentValue) => {
this.track += 1;
if (this.multiplier() % 2 === 0) {
return this.count() * this.multiplier();
}
return currentValue;
});
}

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

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

expect(component.result()).toEqual(undefined);

component.count.set(1);
expect(component.result()).toEqual(undefined);

component.multiplier.set(2);
expect(component.result()).toEqual(2);

component.multiplier.set(3);
expect(component.result()).toEqual(2);
});

it('should track dep properly', () => {
const component = setup();

// kick off computed
expect(component.trackedResult()).toEqual(undefined);
// track increments
expect(component.track).toEqual(1);

// set count only while multipler is still odd
component.count.set(1);
// result is still undefined
expect(component.trackedResult()).toEqual(undefined);
// computed is not reinvoked
expect(component.track).toEqual(1);

// set count only again
component.count.set(2);
// result is still undefined
expect(component.trackedResult()).toEqual(undefined);
// computed is not reinvoked
expect(component.track).toEqual(1);

// set multiplier to an even number
component.multiplier.set(2);
// result is now 4 (multipler * count)
expect(component.trackedResult()).toEqual(4);
// computed is invoked because multiplier changes
expect(component.track).toEqual(2);

// set multiplier back to an odd number
component.multiplier.set(3);
// result is 4 because it returns the currentValue
expect(component.trackedResult()).toEqual(4);
// computed is reinvoked because multiplier changes
expect(component.track).toEqual(3);

// set count while multiplier is an odd number
component.count.set(3);
// result is still 4 because it returns the currentValue
expect(component.trackedResult()).toEqual(4);
// computed is not reinvoked
expect(component.track).toEqual(3);
});
});
22 changes: 22 additions & 0 deletions libs/ngxtension/computed/src/computed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { CreateComputedOptions } from '@angular/core';
import { computed as ngComputed, signal, untracked } from '@angular/core';

export function computed<TValue>(
computedCallback: (currentValue: TValue) => TValue,
options?: CreateComputedOptions<TValue>
) {
if (!options) {
options = { equal: Object.is };
}

const currentValue = signal<TValue>(undefined!, { equal: Object.is });
return ngComputed(() => {
const computedValue = computedCallback(untracked(currentValue));
untracked(() => {
currentValue.set(computedValue);
});
return computedValue;
}, options);
}

export const extendedComputed = computed;
1 change: 1 addition & 0 deletions libs/ngxtension/computed/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './computed';
1 change: 1 addition & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"ngxtension/click-outside": [
"libs/ngxtension/click-outside/src/index.ts"
],
"ngxtension/computed": ["libs/ngxtension/computed/src/index.ts"],
"ngxtension/computed-from": [
"libs/ngxtension/computed-from/src/index.ts"
],
Expand Down

0 comments on commit 7260727

Please sign in to comment.