Skip to content

Commit

Permalink
feat: introduce count(), fix example clear()
Browse files Browse the repository at this point in the history
  • Loading branch information
voznik committed Oct 17, 2023
1 parent 0e0a01c commit 569a932
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.clear-completed:disabled {
color: #999;
cursor: not-allowed;
text-decoration: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*ngxLet="{
todos: todos$ | asyncNoZone,
count: count$ | async,
allDocs: allDocs$ | async,
remainig: remainig$ | async,
filter: filter$ | async
} as $"
Expand Down Expand Up @@ -56,11 +57,13 @@ <h1>todos</h1>
</ul>
</main>
<footer class="footer">
<span class="todo-count" [ngPlural]="$.remainig">
<ng-template ngPluralCase="other">{{ $.remainig }} items left</ng-template>
<ng-template ngPluralCase="=1">one item left</ng-template>
<ng-template ngPluralCase="=0">no items left</ng-template>
</span>
<ng-container *ngIf="showRemainig($.remainig)">
<span class="todo-count" [ngPlural]="$.remainig">
<ng-template ngPluralCase="other">{{ $.remainig }} items left</ng-template>
<ng-template ngPluralCase="=1">one item left</ng-template>
<ng-template ngPluralCase="=0">no items left</ng-template>
</span>
</ng-container>
<ul class="filters">
<li>
<a
Expand Down Expand Up @@ -92,7 +95,7 @@ <h1>todos</h1>
</ul>
<button
class="clear-completed"
[disabled]="$.remaining > 0 || $.count <= 0"
[disabled]="shouldDisableClear($.remainig, $.count)"
(click)="removeCompletedTodos()"
style="z-index: 3"
>
Expand Down
25 changes: 23 additions & 2 deletions examples/demo/src/app/todos/components/todos/todos.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
} from '@angular/core';
import { Observable } from 'rxjs';
import { Todo, TodosFilter } from '../../models';
import { TodosService } from '../../services';
Expand All @@ -13,16 +18,32 @@ export class TodosComponent implements OnInit {
filter$ = this.todosService.filter$;
todos$: Observable<Todo[]> = this.todosService.select();
count$ = this.todosService.count$;
allDocs$ = this.todosService.allDocs$;
remainig$: Observable<number> = this.todosService.remaining$;
newTodo = '';
isEditing = false;

constructor(private todosService: TodosService) {}
constructor(
private todosService: TodosService,
private cdRef: ChangeDetectorRef
) {}

ngOnInit() {
this.todosService.restoreFilter();
}

showRemainig(remaining: number | null) {
return remaining !== null;
}

shouldDisableClear(remaining: number | null, count: number | null) {
if (remaining === null || count === null) {
return true;
}

return remaining > 0 || count === 0;
}

get isAddTodoDisabled() {
return this.newTodo.length < 4;
}
Expand Down
6 changes: 5 additions & 1 deletion examples/demo/src/app/todos/services/todos.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class TodosService {

count$ = this.collectionService.count();

allDocs$ = this.collectionService.allDocs();

remaining$: Observable<number> = this.collectionService.docs().pipe(
map(docs => docs.filter(doc => !doc.completed).length),
tap(remaining => console.log('remaining', remaining))
Expand Down Expand Up @@ -56,8 +58,10 @@ export class TodosService {
}

add(title: string): void {
const id = uuid();
const payload: Todo = {
id: uuid(),
id,
// _id: id,
title,
completed: false,
createdAt: Date.now(),
Expand Down
67 changes: 36 additions & 31 deletions packages/rxdb/collection/src/lib/rxdb-collection.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { InjectionToken } from '@angular/core';
import type { NgxRxdbCollectionConfig } from '@ngx-odm/rxdb/config';
import { NgxRxdbCollectionDump, NgxRxdbError, NgxRxdbService } from '@ngx-odm/rxdb/core';
import {
NgxRxdbCollectionDump,
NgxRxdbCollectionStaticMethods,
NgxRxdbError,
NgxRxdbService,
} from '@ngx-odm/rxdb/core';
import { merge } from '@ngx-odm/rxdb/utils';
import type { PouchAllDocsOptions } from 'rxdb/dist/types/types/pouch';
import type {
MangoQuery,
Expand All @@ -14,12 +20,11 @@ import {
EMPTY,
Observable,
ReplaySubject,
catchError,
defer,
identity,
map,
merge as merge$,
of,
shareReplay,
switchMap,
tap,
} from 'rxjs';
Expand All @@ -38,21 +43,28 @@ type SubscribableOrPromise<T> = {
onrejected?: ((reason: any) => T | PromiseLike<T>) | undefined | null
) => Promise<T>;
};
type LocalDocument = (RxDocumentBase<any> & { isLocal(): true }) | null;
type LocalDocument = (RxDocumentBase<AnyObject> & { isLocal(): true }) | null;
type AllDocsOptions = PouchAllDocsOptions; // & PouchDB.Core.AllDocsWithinRangeOptions;
type NgxRxdbBulkResponse = {
ok: boolean;
id: string;
rev: string;
}[];

type RxCollectionWithStatics<T extends AnyObject> = RxCollection<
T,
// eslint-disable-next-line @typescript-eslint/ban-types
{},
NgxRxdbCollectionStaticMethods
>;

/* eslint-disable jsdoc/require-jsdoc */
/**
* Service for interacting with a RxDB collection.
*/
export type NgxRxdbCollection<T extends AnyObject> = {
readonly db: Readonly<RxDatabase>;
readonly collection: Readonly<RxCollection<T>>;
initialized$: Observable<boolean>;
readonly collection: RxCollectionWithStatics<T>;
initialized$: Observable<unknown>;

destroy(): void;
info(): SubscribableOrPromise<any>;
Expand All @@ -61,7 +73,7 @@ export type NgxRxdbCollection<T extends AnyObject> = {

docs(query?: MangoQuery<T>): Observable<RxDocument<T>[]>;
docsByIds(ids: string[]): Observable<RxDocument<T>[]>;
allDocs(options: AllDocsOptions): Observable<T[]>;
allDocs(options?: AllDocsOptions): Observable<T[]>;
count(): Observable<number>;

get(id: string): Observable<RxDocument<T> | null>;
Expand All @@ -79,12 +91,13 @@ export type NgxRxdbCollection<T extends AnyObject> = {
setLocal(id: string, prop: string, value: any): SubscribableOrPromise<any>;
removeLocal(id: string): SubscribableOrPromise<boolean>;
};
/* eslint-enable jsdoc/require-jsdoc */

/**
* Injection token for Service for interacting with a RxDB collection.
* This token is used to inject an instance of NgxRxdbCollection into a component or service.
*/
export const NgxRxdbCollectionService = new InjectionToken<NgxRxdbCollection<any>>(
export const NgxRxdbCollectionService = new InjectionToken<NgxRxdbCollection<AnyObject>>(
'NgxRxdbCollection'
);

Expand All @@ -104,15 +117,15 @@ export function collectionServiceFactory(config: NgxRxdbCollectionConfig) {
export class NgxRxdbCollectionServiceImpl<T extends AnyObject>
implements NgxRxdbCollection<T>
{
private _collection!: RxCollection<T>;
private _init$ = new ReplaySubject<any>();
private _collection!: RxCollectionWithStatics<T>;
private _init$ = new ReplaySubject();

get initialized$(): Observable<boolean> {
get initialized$(): Observable<unknown> {
return this._init$.asObservable();
}

get collection(): Readonly<RxCollection<T>> {
return this._collection;
get collection(): RxCollectionWithStatics<T> {
return this._collection as RxCollectionWithStatics<T>;
}

get db(): Readonly<RxDatabase> {
Expand All @@ -126,7 +139,7 @@ export class NgxRxdbCollectionServiceImpl<T extends AnyObject>
dbService
.initCollection(this.config)
.then((collection: RxCollection) => {
this._collection = collection;
this._collection = collection as RxCollectionWithStatics<T>;
this._init$.next(true);
this._init$.complete();
})
Expand Down Expand Up @@ -180,34 +193,26 @@ export class NgxRxdbCollectionServiceImpl<T extends AnyObject>
}

@collectionMethod({ startImmediately: false, asObservable: true })
allDocs(options: AllDocsOptions): Observable<T[]> {
allDocs(options: AllDocsOptions = {}): Observable<T[]> {
const defaultOptions = {
include_docs: true,
attachments: false,
startkey: '_design\uffff', // INFO: to skip design docs
};
options = merge(defaultOptions, options);
return defer(async () => {
const result = await this.collection.pouch
.allDocs({ ...defaultOptions, ...options })
.catch(e => {
// debug(e);
return { rows: [] };
});
const result = await this.collection.pouch.allDocs(options).catch(e => {
return { rows: [] };
});
return result.rows.map(({ doc, id }) => ({ ...doc, id }));
});
}

count(): Observable<number> {
const defaultOptions = {
include_docs: false,
attachments: false,
startkey: '_design\uffff', // INFO: to skip design docs
};
return this.initialized$.pipe(
switchMap(() => this.collection.$),
switchMap(() => this.collection.pouch.allDocs(defaultOptions)),
catchError(() => of({ total_rows: 0 })),
map(result => result.total_rows)
switchMap(() => merge$(this.collection.insert$, this.collection.remove$)),
switchMap(() => this.collection.countAllDocuments()),
tap(console.debug)
);
}

Expand Down
29 changes: 16 additions & 13 deletions packages/rxdb/core/src/lib/rxdb-collection.class.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
/* eslint-disable @typescript-eslint/ban-types */
import { NgxRxdbCollectionConfig } from '@ngx-odm/rxdb/config';
import {
PouchDBInstance,
PouchSettings,
RxCollectionCreator,
RxJsonSchema,
} from 'rxdb/plugins/core';
import type { NgxRxdbCollectionConfig } from '@ngx-odm/rxdb/config';
import type { KeyFunctionMap, PouchSettings, RxCollectionCreator } from 'rxdb/plugins/core';
import { RxJsonSchema } from 'rxdb/plugins/core';

async function infoFn(this: { pouch: PouchDBInstance }): Promise<any> {
async function infoFn(this: {
pouch: PouchDB.Database;
}): Promise<PouchDB.Core.DatabaseInfo> {
return await this.pouch.info();
}

async function countAllDocumentsFn(this: { pouch: PouchDBInstance }): Promise<number> {
async function countAllDocumentsFn(this: { pouch: PouchDB.Database }): Promise<number> {
const res = await this.pouch.allDocs({
include_docs: false,
attachments: false,
deleted: 'ok',
startkey: '_design\uffff',
// deleted: 'ok',
startkey: '_design\uffff', // Omit design doc
});
return res.rows.length;
return res.total_rows - 1; // Omit design doc
}

export type NgxRxdbCollectionStaticMethods = KeyFunctionMap & {
info(): Promise<PouchDB.Core.DatabaseInfo>;
countAllDocuments(): Promise<number>;
};

const DEFAULT_INSTANCE_METHODS: Record<string, Function> = {};
const DEFAULT_COLLECTION_METHODS: Record<string, Function> = {
info: infoFn,
Expand All @@ -36,7 +39,7 @@ export class NgxRxdbCollectionCreator implements RxCollectionCreator {
schema!: RxJsonSchema;
pouchSettings?: NgxRxdbCollectionConfig['pouchSettings'];
migrationStrategies?: NgxRxdbCollectionConfig['migrationStrategies'];
statics?: NgxRxdbCollectionConfig['statics'] & keyof typeof DEFAULT_COLLECTION_METHODS;
statics?: NgxRxdbCollectionStaticMethods;
methods?: NgxRxdbCollectionConfig['methods'];
attachments?: NgxRxdbCollectionConfig['attachments'];
options?: NgxRxdbCollectionConfig['options'];
Expand Down
2 changes: 1 addition & 1 deletion packages/rxdb/core/src/lib/rxdb.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, Injectable, StaticProvider } from '@angular/core';
import { Inject, Injectable } from '@angular/core';
import {
DEFAULT_BACKOFF_FN,
NgxRxdbCollectionConfig,
Expand Down
1 change: 1 addition & 0 deletions packages/rxdb/utils/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
type Cast<I, O> = Exclude<I, O> extends never ? I : O;
type Nil = null | undefined;
type EmptyObject = Record<string, never>;
Expand Down

0 comments on commit 569a932

Please sign in to comment.