Skip to content

Commit

Permalink
fix: leader election & todos issues
Browse files Browse the repository at this point in the history
  • Loading branch information
voznik committed Oct 16, 2023
1 parent aff7417 commit 0e0a01c
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 127 deletions.
105 changes: 56 additions & 49 deletions examples/demo/src/app/todos/components/todos/todos.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<!-- https://angular.io/guide/template-typecheck#disabling-type-checking-using-any -->
<section class="todoapp" *ngIf="todos$ | async as todos">
<section
class="todoapp"
*ngxLet="{
todos: todos$ | asyncNoZone,
count: count$ | async,
remainig: remainig$ | async,
filter: filter$ | async
} as $"
>
<header class="header">
<h1>todos</h1>
<input
Expand All @@ -13,17 +21,17 @@ <h1>todos</h1>
/>
</header>
<main class="main">
<input
<!-- <input
id="toggle-all"
class="toggle-all"
type="checkbox"
*ngIf="todos.length"
*ngIf="true"
#toggleall
[checked]="(filter$ | async) === 'ALL'"
(click)="filterTodos('')"
/>
/> -->
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.completed">
<li *ngFor="let todo of $.todos" [class.completed]="todo.completed">
<div class="view">
<input
class="toggle"
Expand All @@ -47,49 +55,48 @@ <h1>todos</h1>
</li>
</ul>
</main>
<footer class="footer" *ngIf="filter$ | async as filter">
<ng-container *ngIf="remainig$ | async as remainig">
<span class="todo-count" [ngPlural]="remainig.length">
<ng-template ngPluralCase="other">{{ remainig.length }} items left</ng-template>
<ng-template ngPluralCase="=1">one item left</ng-template>
<ng-template ngPluralCase="=0">no items left</ng-template>
</span>
<ul class="filters">
<li>
<a
href="javascript:void(0);"
(click)="filterTodos('ALL')"
[ngClass]="{ selected: filter == 'ALL' }"
>
All
</a>
</li>
<li>
<a
href="javascript:void(0);"
(click)="filterTodos('ACTIVE')"
[ngClass]="{ selected: filter == 'ACTIVE' }"
>
Active
</a>
</li>
<li>
<a
href="javascript:void(0);"
(click)="filterTodos('COMPLETED')"
[ngClass]="{ selected: filter == 'COMPLETED' }"
>
Completed
</a>
</li>
</ul>
<button
class="clear-completed"
(click)="removeCompletedTodos()"
*ngIf="todos.length > remainig.length"
>
Clear completed
</button>
</ng-container>
<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>
<ul class="filters">
<li>
<a
href="javascript:void(0);"
(click)="filterTodos('ALL')"
[ngClass]="{ selected: $.filter == 'ALL' }"
>
All
</a>
</li>
<li>
<a
href="javascript:void(0);"
(click)="filterTodos('ACTIVE')"
[ngClass]="{ selected: $.filter == 'ACTIVE' }"
>
Active
</a>
</li>
<li>
<a
href="javascript:void(0);"
(click)="filterTodos('COMPLETED')"
[ngClass]="{ selected: $.filter == 'COMPLETED' }"
>
Completed
</a>
</li>
</ul>
<button
class="clear-completed"
[disabled]="$.remaining > 0 || $.count <= 0"
(click)="removeCompletedTodos()"
style="z-index: 3"
>
Clear completed
</button>
</footer>
</section>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { TodosService } from '../../services';
export class TodosComponent implements OnInit {
filter$ = this.todosService.filter$;
todos$: Observable<Todo[]> = this.todosService.select();
remainig$: Observable<Todo[]> = this.todosService.select(true);
count$ = this.todosService.count$;
remainig$: Observable<number> = this.todosService.remaining$;
newTodo = '';
isEditing = false;

Expand Down
3 changes: 2 additions & 1 deletion examples/demo/src/app/todos/models/todos.schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { NgxRxdbCollectionConfig } from '@ngx-odm/rxdb/config';
import { RxCollection } from 'rxdb';
import { initialState } from './todos.model';

export async function percentageCompletedFn() {
const allDocs = await this.find().exec();
const allDocs = await (this as RxCollection).find().exec();
return allDocs.filter(doc => !!doc.completed).length / allDocs.length;
}
const collectionMethods = {
Expand Down
51 changes: 37 additions & 14 deletions examples/demo/src/app/todos/services/todos.service.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
/* eslint-disable no-console */
import { Location } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { NgxRxdbCollection, NgxRxdbCollectionService } from '@ngx-odm/rxdb/collection';
import { Observable, ReplaySubject, Subject } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { MangoQuery } from 'rxdb/dist/types/types';
import { Observable } from 'rxjs';
import { distinctUntilChanged, startWith, switchMap, map, tap } from 'rxjs/operators';
import { v4 as uuid } from 'uuid';
import { Todo, TodosFilter } from '../models';
import { MangoQuery } from 'rxdb/dist/types/types';

@Injectable()
export class TodosService {
private _filter$ = new Subject<TodosFilter>();
filter$ = this._filter$.asObservable();
filter$ = this.collectionService.getLocal('local', 'filterValue').pipe(
startWith('ALL'),
distinctUntilChanged(),
tap(filterValue => console.log('filterValue', filterValue))
);

count$ = this.collectionService.count();

remaining$: Observable<number> = this.collectionService.docs().pipe(
map(docs => docs.filter(doc => !doc.completed).length),
tap(remaining => console.log('remaining', remaining))
);

constructor(
@Inject(NgxRxdbCollectionService) private collectionService: NgxRxdbCollection<Todo>
) {}
@Inject(NgxRxdbCollectionService) private collectionService: NgxRxdbCollection<Todo>,
private location: Location
) {
this.collectionService.initialized$
.pipe(switchMap(() => this.getCount()))
.subscribe(count => {
console.debug('count', count);
});
}

async getCount() {
const count = await this.collectionService.collection?.['countAllDocuments']?.();
return count;
}

select(completedOnly = false): Observable<Todo[]> {
const queryObj = this.buildQueryObject(completedOnly);
Expand Down Expand Up @@ -57,16 +80,16 @@ export class TodosService {
}

restoreFilter(): void {
this.collectionService.getLocal('local').subscribe((local: any) => {
const filterValue = local?.get('filterValue');
this.changeFilter(filterValue || 'ALL');
});
const query = this.location.path().split('?')[1];
const searchParams = new URLSearchParams(query);
const filterValue = searchParams.get('filter') || 'ALL';
this.collectionService.upsertLocal('local', { filterValue });
}

changeFilter(filterValue: TodosFilter): void {
this.collectionService.upsertLocal('local', { filterValue }).subscribe(local => {
this._filter$.next(filterValue);
});
const path = this.location.path().split('?')[0];
this.location.replaceState(path, `filter=${filterValue}`);
this.collectionService.upsertLocal('local', { filterValue });
}

private buildQueryObject(completedOnly: boolean): MangoQuery<Todo> {
Expand Down
2 changes: 1 addition & 1 deletion packages/rxdb/collection/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './lib/rxdb-collection.module';
export * from './lib/rxdb-collection.service';
export * from './lib/rxdb-collection.helpers';
export * from './lib/rxdb-async-no-zone.pipe';
export * from './lib/rxdb-collection.pipe';
32 changes: 0 additions & 32 deletions packages/rxdb/collection/src/lib/rxdb-async-no-zone.pipe.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/rxdb/collection/src/lib/rxdb-collection.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Inject, NgModule } from '@angular/core';
import { NgxRxdbAsyncNoZonePipe } from './rxdb-async-no-zone.pipe';
import { NgxRxdbAsyncNoZonePipe, NgxRxdbLetDirective } from './rxdb-collection.pipe';
import { NgxRxdbCollectionService, NgxRxdbCollection } from './rxdb-collection.service';

/**
Expand All @@ -10,15 +10,15 @@ import { NgxRxdbCollectionService, NgxRxdbCollection } from './rxdb-collection.s
* this module actually creates a collection with collectionService and provided config
*/
@NgModule({
declarations: [NgxRxdbAsyncNoZonePipe],
exports: [NgxRxdbAsyncNoZonePipe],
declarations: [NgxRxdbAsyncNoZonePipe, NgxRxdbLetDirective],
exports: [NgxRxdbAsyncNoZonePipe, NgxRxdbLetDirective],
})
export class NgxRxdbFeatureModule {
constructor(

Check warning on line 17 in packages/rxdb/collection/src/lib/rxdb-collection.module.ts

View workflow job for this annotation

GitHub Actions / build

Missing JSDoc comment
@Inject(NgxRxdbCollectionService) private collectionService: NgxRxdbCollection<any>

Check warning on line 18 in packages/rxdb/collection/src/lib/rxdb-collection.module.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
) {
collectionService.info().subscribe!(info => {

Check warning on line 20 in packages/rxdb/collection/src/lib/rxdb-collection.module.ts

View workflow job for this annotation

GitHub Actions / build

Forbidden non-null assertion
// console.log('NgxRxdbFeatureModule:collectionService:info', info);
console.debug('collectionService:info', info);
});
}
}
90 changes: 90 additions & 0 deletions packages/rxdb/collection/src/lib/rxdb-collection.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* eslint-disable jsdoc/require-jsdoc */
import { AsyncPipe } from '@angular/common';
import {
Directive,
Input,
OnInit,
TemplateRef,
ViewContainerRef,
Pipe,
PipeTransform,
} from '@angular/core';

/**
* @deprecated
* @see https://github.com/pubkey/rxdb/blob/master/examples/angular/src/app/pipes/async-no-zone.pipe.ts
* Because RxDB calculates queries with caching and things,
* they do not run in angulars zone.
* @link https://stackoverflow.com/questions/35513015/async-pipe-not-rendering-the-stream-updates

Check warning on line 18 in packages/rxdb/collection/src/lib/rxdb-collection.pipe.ts

View workflow job for this annotation

GitHub Actions / build

Invalid JSDoc tag name "link"
* To not have to run changeDetection on each emit for each subscribed query,
* we use a different async-pipe that runs the change-detection on emit.
* @note in Angular 10+ you may need to disable checking of a binding expression
* by surrounding the expression in a call to the $any() cast pseudo-function
* in your html template, e.g.
*
* ``` *ngIf="$any(todos$ | asyncNoZone) as todos"> ```
*/
@Pipe({
name: 'asyncNoZone',
pure: false,
})
export class NgxRxdbAsyncNoZonePipe extends AsyncPipe implements PipeTransform {}
// monkeypatch the private method with detectChanges() instead of markForCheck()
NgxRxdbAsyncNoZonePipe.prototype['_updateLatestValue'] = function (
async: unknown,
value: Record<string, unknown>
): void {
if (async === this['_obj']) {
this['_latestValue'] = value;
this['_ref'].detectChanges();
}
};

///////////////////////////////////

export class NgxLetContext {
$implicit: unknown = null;
ngxLet: unknown = null;
}
/**
* We often use *ngIf="stream$ | async as stream" to subscribe to an observable property and
* rename it to a template variable. But with nested template, *ngIf might remove your template which may not be expected.
*
* `*ngxLet` just hold a reference to the result of `async` pipe in a template variable and
* don't have any special logic like structure directives such as `*ngIf` or `*ngFor`
* so it run faster and very handy.
* You can also subscribe to multiple observable separately with `*ngxLet` like this:
* @example ```html
* <ng-container
* ngxLet="{
* device: device$ | async,
* date: filterDate$ | async
* } as options">
* <pick-date
* [registeredAt]="options.device?.registeredAt"
* [firstDate]="options.date?.from"
* [secondDate]="options.date?.to"
* ></pick-date>
* </ng-container>
* ```
*/
@Directive({
selector: '[ngxLet]',
})
export class NgxRxdbLetDirective implements OnInit {
private _context = new NgxLetContext();

@Input()
set ngxLet(value: unknown) {
this._context.$implicit = this._context.ngxLet = value;
}

constructor(
private _vcr: ViewContainerRef,
private _templateRef: TemplateRef<NgxLetContext>
) {}

ngOnInit() {
this._vcr.createEmbeddedView(this._templateRef, this._context);
}
}
Loading

0 comments on commit 0e0a01c

Please sign in to comment.