Skip to content

Commit

Permalink
feat: omit need to unwrap query ref
Browse files Browse the repository at this point in the history
feat: add `withMutation` feature

Release-As: 0.2.0
  • Loading branch information
k3nsei committed Aug 16, 2024
1 parent 1cb2edb commit 1d6b3a8
Show file tree
Hide file tree
Showing 35 changed files with 9,353 additions and 4,165 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ jobs:
- name: Install dependencies
uses: ./.github/actions/install-npm-deps

- name: Lint
run: npx --no -- ng lint

- name: Build
run: npx --no -- ng build ngx-signal-store-query
run: |
npx --no -- ng build ngx-signal-store-query --configuration=production
npx --no -- ng build demo --configuration=production --progress=false --verbose
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Build
if: ${{ steps.release.outputs.release_created }}
shell: bash
run: npx --no -- ng build ngx-signal-store-query
run: npx --no -- ng build ngx-signal-store-query --configuration=production

- name: Copy extra files
if: ${{ steps.release.outputs.release_created }}
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ import { ExampleStore } from './example.store.ts';
selector: 'app-example',
template: `
<pre>
Loading: {{ store.exampleQuery().isLoading() }}
Fetching: {{ store.exampleQuery().isFetching() }}
Loading: {{ store.exampleQuery.isLoading() }}
Fetching: {{ store.exampleQuery.isFetching() }}
Data:
{{ store.exampleQuery().data() | json }}
{{ store.exampleQuery.data() | json }}
</pre>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
Expand Down
Empty file added apps/.gitkeep
Empty file.
Empty file.
3,326 changes: 3,326 additions & 0 deletions apps/demo/public/assets/data/gh-org-repos-angular.json

Large diffs are not rendered by default.

3,332 changes: 3,332 additions & 0 deletions apps/demo/public/assets/data/gh-org-repos-google.json

Large diffs are not rendered by default.

48 changes: 5 additions & 43 deletions apps/demo/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,44 +1,6 @@
@if (isFetching() || isLoading()) {
<mat-progress-bar class="busy-indicator" mode="indeterminate" />
}

<mat-list class="list">
<div mat-subheader>{{ organization() | titlecase }} Repositories</div>

@if (isFetching() || isLoading()) {
<ng-container *ngTemplateOutlet="skeleton" />
<ng-container *ngTemplateOutlet="skeleton" />
<ng-container *ngTemplateOutlet="skeleton" />
} @else {
@for (repo of data(); track repo) {
<mat-list-item class="list-item">
<mat-icon class="list-item__icon" matListItemIcon>folder</mat-icon>
<div class="list-item__title" matListItemTitle>
<a class="list-item__link" [href]="repo.html_url" target="_blank" rel="external nofollow noopener noreferrer">
{{ repo.name }}
</a>
<span class="list-item__pill">{{ repo.private ? 'Private' : 'Public' }}</span>
</div>
<div class="list-item__description" matListItemLine>
{{ repo.description }}
</div>
</mat-list-item>
} @empty {
<div class="no-records">No repositories found</div>
}
}
</mat-list>
<ssq-counter />
<ssq-github-repos />

<ng-template #skeleton>
<mat-list-item class="list-item skeleton">
<mat-icon class="list-item__icon" matListItemIcon>folder</mat-icon>
<div class="list-item__title" matListItemTitle>
<ssq-skeleton-text />
</div>
<div class="list-item__description" matListItemLine>
<ssq-skeleton-text inlineSize="12rem" />
</div>
</mat-list-item>
</ng-template>

<angular-query-devtools />
@defer (when isDevMode) {
<angular-query-devtools />
}
41 changes: 4 additions & 37 deletions apps/demo/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,40 +1,7 @@
@use '@angular/material' as mat;
@use 'theme' as *;

@mixin apply-theme {
--ssq-chip-border-color: #{mat.get-theme-color($light-theme, on-surface)};

@media (prefers-color-scheme: dark) {
--ssq-chip-border-color: #{mat.get-theme-color($dark-theme, on-surface)};
}
}

:host {
@include apply-theme;

margin-inline: auto;
padding: 2rem;
inline-size: clamp(20rem, 100%, 64rem);
min-block-size: 100%;
display: block;

.busy-indicator {
inset: 0 auto 0;
position: fixed;
}

.list-item {
:is(&__pill) {
padding: 0.05rem 0.375rem;
translate: 0.125rem -0.25rem;
display: inline-grid;
align-items: center;
border: 0.05rem solid hsl(from var(--ssq-chip-border-color) h s l / 50%);
border-radius: 2em;
font-size: 0.75rem;
}
}

.no-records {
display: block;
place-content: center;
text-align: center;
opacity: 0.5;
}
}
47 changes: 6 additions & 41 deletions apps/demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,19 @@
import { NgTemplateOutlet, TitleCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';

import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { ChangeDetectionStrategy, Component, isDevMode } from '@angular/core';

import { AngularQueryDevtools } from '@tanstack/angular-query-devtools-experimental';

import { AppStore } from './app.store';
import { SkeletonTextComponent } from './skeleton-text.component';
import { CounterComponent } from './counter';
import { GithubReposComponent } from './gh-repos';
import { SkeletonTextComponent } from './skeleton-text';

@Component({
standalone: true,
selector: 'ssq-app',
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [AppStore],
imports: [
TitleCasePipe,
MatIconModule,
MatListModule,
MatProgressBarModule,
AngularQueryDevtools,
SkeletonTextComponent,
NgTemplateOutlet,
],
imports: [AngularQueryDevtools, CounterComponent, GithubReposComponent, SkeletonTextComponent],
})
export class AppComponent {
protected readonly store = inject(AppStore);

protected organization = this.store.organization;

protected githubQuery = this.store.githubQuery;

protected isFetching = computed(() => {
const githubQuery = this.githubQuery();

return githubQuery.isFetching();
});

protected isLoading = computed(() => {
const githubQuery = this.githubQuery();

return githubQuery.isLoading();
});

protected data = computed(() => {
const githubQuery = this.githubQuery();

return githubQuery.data() ?? [];
});
protected readonly isDevMode = isDevMode();
}
6 changes: 4 additions & 2 deletions apps/demo/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import { type ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';

import { provideAngularQuery, QueryClient } from '@tanstack/angular-query-experimental';

import { githubApiInterceptor } from './gh-repos';

export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideClientHydration(),
provideAnimationsAsync(),
provideHttpClient(withFetch()),
provideHttpClient(withFetch(), withInterceptors([githubApiInterceptor])),
provideAngularQuery(
new QueryClient({
defaultOptions: {
Expand Down
9 changes: 9 additions & 0 deletions apps/demo/src/app/counter/counter.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<output aria-label="Current count">{{ value() }}</output>

<button mat-fab type="button" aria-label="Increase" title="Increase" [disabled]="isBusy()" (click)="increase()">
<mat-icon fontIcon="exposure_plus_1" />
</button>

<button mat-fab type="button" aria-label="Decrease" title="Decrease" [disabled]="isBusy()" (click)="decrease()">
<mat-icon fontIcon="exposure_neg_1" />
</button>
22 changes: 22 additions & 0 deletions apps/demo/src/app/counter/counter.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@use '@angular/material' as mat;

@use 'theme' as *;

:host {
margin-inline: auto;
min-inline-size: 6rem;
inline-size: fit-content;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto 3rem;
justify-items: center;
gap: 0.5rem;

output {
grid-column: 1 / 3;
display: block;
align-content: center;
text-align: center;
font: #{mat.get-theme-typography($light-theme, headline-large, font)};
}
}
36 changes: 36 additions & 0 deletions apps/demo/src/app/counter/counter.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';

import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';

import { CounterStore } from './counter.store';

@Component({
standalone: true,
selector: 'ssq-counter',
templateUrl: './counter.component.html',
styleUrl: './counter.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [CounterStore],
imports: [MatButtonModule, MatIconModule],
})
export class CounterComponent {
private readonly store = inject(CounterStore);

protected readonly value = this.store.count;

public readonly isBusy = computed(() => {
const increase = this.store.increaseMutation.isPending();
const decrease = this.store.decreaseMutation.isPending();

return increase || decrease;
});

public increase(): void {
this.store.increaseMutation.mutate(1);
}

public decrease(): void {
this.store.decreaseMutation.mutate(1);
}
}
76 changes: 76 additions & 0 deletions apps/demo/src/app/counter/counter.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { DestroyRef, inject } from '@angular/core';

import { MatSnackBar } from '@angular/material/snack-bar';

import { patchState, signalStore, withState } from '@ngrx/signals';
import { withMutation } from '@ngx-signal-store-query/core';

export const CounterStore = signalStore(
withState({ count: 0 }),
withMutation('increase', (store) => () => {
const destroyRef = inject(DestroyRef);
const snackBar = inject(MatSnackBar);

let timer: ReturnType<typeof setTimeout> | null = null;

destroyRef.onDestroy(() => timer != null && clearTimeout(timer));

return {
mutationFn(amount: number): Promise<CounterResponse> {
const count = store.count();

return new Promise((resolve, reject) => {
if (count >= 5) {
return reject(new RangeError('Count is to big'));
}

timer = setTimeout(() => resolve({ count: count + amount }), 250);
});
},
onSuccess({ count }: CounterResponse): void {
return patchState(store, { count });
},
onError(error: Error): void {
snackBar.open(error.message, '', {
panelClass: 'popover-error',
duration: 5000,
});
},
};
}),
withMutation('decrease', (store) => () => {
const destroyRef = inject(DestroyRef);
const snackBar = inject(MatSnackBar);

let timer: ReturnType<typeof setTimeout> | null = null;

destroyRef.onDestroy(() => timer != null && clearTimeout(timer));

return {
mutationFn: (amount: number): Promise<CounterResponse> => {
const count = store.count();

return new Promise((resolve, reject) => {
if (count <= 0) {
return reject(new RangeError('Count is to low'));
}

timer = setTimeout(() => resolve({ count: count - amount }), 250);
});
},
onSuccess: ({ count }: CounterResponse): void => {
return patchState(store, { count });
},
onError: (error: Error): void => {
snackBar.open(error.message, '', {
panelClass: 'popover-error',
duration: 5000,
});
},
};
}),
);

interface CounterResponse {
count: number;
}
1 change: 1 addition & 0 deletions apps/demo/src/app/counter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './counter.component';
Loading

0 comments on commit 1d6b3a8

Please sign in to comment.