diff --git a/docs/README.md b/docs/README.md index ab158f1..bf6067a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -154,6 +154,23 @@ managing the DOM. (If you were to build a "push" alternative to Angular's Reacti for instance.) In such cases there are "Immediate" variants of bindings. You may not need them, and the defaults should do what you need in most cases. +### Error Blinkenlights + +Pharkas provides a set of blinkenlights (lights intended to blink a status) for very basic error +status indication. These are "last chance error reporting" blinkenlights for generic error +situations when any observable provided to `bind` or `bindEffect` or an "Immediate" variant of +such has thrown an error. + +`pharkasTemplateStopped` in particular signals that a component has effectively _stopped_ updating just +about entirely as Pharkas will stop notifying Angular Change Detection when it raises +`pharkasTemplateStopped` during observation of an error from the combined change detection observable +for non-"Immediate" observables. + +It is encouraged to move error detection and avoidance strategies such as retries up into your +observable pipelines themselves, but sometimes you need a last chance way to detect that the +worst has happened, the component may be stopped/stuck, and in that case render some sort of +frowny face 😟. + ## Callbacks Callbacks are very useful for everything from consuming output bindings of other Angular controls to diff --git a/docs/classes/PharkasComponent.md b/docs/classes/PharkasComponent.md index ea19f91..0b67bc2 100644 --- a/docs/classes/PharkasComponent.md +++ b/docs/classes/PharkasComponent.md @@ -25,6 +25,14 @@ inspirations from ReactiveUI (.NET) and React's Hook components. - [constructor](PharkasComponent.md#constructor) +### Accessors + +- [pharkasEffectError](PharkasComponent.md#pharkaseffecterror) +- [pharkasError](PharkasComponent.md#pharkaserror) +- [pharkasImmediateTemplateError](PharkasComponent.md#pharkasimmediatetemplateerror) +- [pharkasTemplateError](PharkasComponent.md#pharkastemplateerror) +- [pharkasTemplateStopped](PharkasComponent.md#pharkastemplatestopped) + ### Methods - [bind](PharkasComponent.md#bind) @@ -60,7 +68,90 @@ inspirations from ReactiveUI (.NET) and React's Hook components. #### Defined in -[pharkas.component.ts:77](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L77) +[pharkas.component.ts:129](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L129) + +## Accessors + +### pharkasEffectError + +• `get` **pharkasEffectError**(): `boolean` + +An error has been observed in any observable applied to `bindEffect` or +`bindEffectImmediate`. + +#### Returns + +`boolean` + +#### Defined in + +[pharkas.component.ts:104](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L104) + +___ + +### pharkasError + +• `get` **pharkasError**(): `boolean` + +An error has been observed in any observable applied to `bind`, `bindImmediate`, +`bindEffect`, or `bindEffectImmediate`. + +#### Returns + +`boolean` + +#### Defined in + +[pharkas.component.ts:93](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L93) + +___ + +### pharkasImmediateTemplateError + +• `get` **pharkasImmediateTemplateError**(): `boolean` + +An error has been observed in any observable applied to `bindImmediate`. + +#### Returns + +`boolean` + +#### Defined in + +[pharkas.component.ts:117](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L117) + +___ + +### pharkasTemplateError + +• `get` **pharkasTemplateError**(): `boolean` + +An error has been observed in any observable applied to `bind` or `bindImmediate`. + +#### Returns + +`boolean` + +#### Defined in + +[pharkas.component.ts:123](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L123) + +___ + +### pharkasTemplateStopped + +• `get` **pharkasTemplateStopped**(): `boolean` + +An error has been observed in any observable applied to `bind`. Change detection +has *stopped* for all `bind` bound template observables. + +#### Returns + +`boolean` + +#### Defined in + +[pharkas.component.ts:111](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L111) ## Methods @@ -93,7 +184,7 @@ Default bound observation is combined and throttled to requestAnimationFrame for #### Defined in -[pharkas.component.ts:191](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L191) +[pharkas.component.ts:243](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L243) ___ @@ -124,7 +215,7 @@ ___ #### Defined in -[pharkas.component.ts:306](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L306) +[pharkas.component.ts:358](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L358) ___ @@ -157,7 +248,7 @@ Immediate bindings are neither combined nor throttled. #### Defined in -[pharkas.component.ts:218](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L218) +[pharkas.component.ts:270](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L270) ___ @@ -188,7 +279,7 @@ ___ #### Defined in -[pharkas.component.ts:331](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L331) +[pharkas.component.ts:386](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L386) ___ @@ -217,7 +308,7 @@ Bind an observable to an `@Output()`. #### Defined in -[pharkas.component.ts:244](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L244) +[pharkas.component.ts:296](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L296) ___ @@ -247,7 +338,7 @@ value #### Defined in -[pharkas.component.ts:173](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L173) +[pharkas.component.ts:225](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L225) ___ @@ -279,7 +370,7 @@ Callback function #### Defined in -[pharkas.component.ts:258](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L258) +[pharkas.component.ts:310](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L310) ___ @@ -297,7 +388,7 @@ OnDestroy.ngOnDestroy #### Defined in -[pharkas.component.ts:389](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L389) +[pharkas.component.ts:464](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L464) ___ @@ -315,7 +406,7 @@ OnInit.ngOnInit #### Defined in -[pharkas.component.ts:351](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L351) +[pharkas.component.ts:409](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L409) ___ @@ -346,7 +437,7 @@ Set an input value #### Defined in -[pharkas.component.ts:133](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L133) +[pharkas.component.ts:185](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L185) ___ @@ -378,7 +469,7 @@ Observable #### Defined in -[pharkas.component.ts:281](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L281) +[pharkas.component.ts:333](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L333) ___ @@ -412,4 +503,4 @@ Observable #### Defined in -[pharkas.component.ts:154](/~https://github.com/WorldMaker/angular-pharkas/blob/b6a6991/projects/angular-pharkas/src/pharkas.component.ts#L154) +[pharkas.component.ts:206](/~https://github.com/WorldMaker/angular-pharkas/blob/0eb4ab5/projects/angular-pharkas/src/pharkas.component.ts#L206) diff --git a/projects/angular-pharkas/README.md b/projects/angular-pharkas/README.md index 61a3a0d..a735cda 100644 --- a/projects/angular-pharkas/README.md +++ b/projects/angular-pharkas/README.md @@ -152,6 +152,23 @@ managing the DOM. (If you were to build a "push" alternative to Angular's Reacti for instance.) In such cases there are "Immediate" variants of bindings. You may not need them, and the defaults should do what you need in most cases. +### Error Blinkenlights + +Pharkas provides a set of blinkenlights (lights intended to blink a status) for very basic error +status indication. These are "last chance error reporting" blinkenlights for generic error +situations when any observable provided to `bind` or `bindEffect` or an "Immediate" variant of +such has thrown an error. + +`pharkasTemplateStopped` in particular signals that a component has effectively _stopped_ updating just +about entirely as Pharkas will stop notifying Angular Change Detection when it raises +`pharkasTemplateStopped` during observation of an error from the combined change detection observable +for non-"Immediate" observables. + +It is encouraged to move error detection and avoidance strategies such as retries up into your +observable pipelines themselves, but sometimes you need a last chance way to detect that the +worst has happened, the component may be stopped/stuck, and in that case render some sort of +frowny face 😟. + ## Callbacks Callbacks are very useful for everything from consuming output bindings of other Angular controls to diff --git a/projects/angular-pharkas/package.json b/projects/angular-pharkas/package.json index 73f6f59..5633d74 100644 --- a/projects/angular-pharkas/package.json +++ b/projects/angular-pharkas/package.json @@ -1,6 +1,6 @@ { "name": "angular-pharkas", - "version": "1.0.0", + "version": "2.0.0", "peerDependencies": { "@angular/common": "^11.1.1", "@angular/core": "^11.1.1" diff --git a/projects/angular-pharkas/src/pharkas.component.ts b/projects/angular-pharkas/src/pharkas.component.ts index e7636c7..1539b85 100644 --- a/projects/angular-pharkas/src/pharkas.component.ts +++ b/projects/angular-pharkas/src/pharkas.component.ts @@ -10,7 +10,6 @@ import { import { animationFrameScheduler, BehaviorSubject, - from, isObservable, merge, Observable, @@ -18,7 +17,7 @@ import { Subject, Subscription, } from 'rxjs' -import { debounceTime, map, mergeAll, observeOn, share } from 'rxjs/operators' +import { debounceTime, observeOn, share } from 'rxjs/operators' const subscription = Symbol('subscription') const props = Symbol('props') @@ -48,6 +47,12 @@ interface PharkasCallback { type PharkasProp = PharkasInput | PharkasDisplay | PharkasCallback +interface PharkasMeta { + templateError: boolean + immediateTemplateError: boolean + effectError: boolean +} + function bindSubject(observable: Observable, subject: Subject) { // Zone's monkey patching of RxJS breaks the obvious binding (observable.subscribe(subject)) // as RxJS doesn't think it "safe" so we need to do this the hard way to support migrating @@ -73,6 +78,53 @@ function bindSubject(observable: Observable, subject: Subject) { export class PharkasComponent implements OnInit, OnDestroy { private [subscription] = new Subscription() private [props]: Map> = new Map() + private [pharkas]: PharkasMeta = { + effectError: false, + immediateTemplateError: false, + templateError: false, + } + + //#region *** Blinkenlights *** + + /** + * An error has been observed in any observable applied to `bind`, `bindImmediate`, + * `bindEffect`, or `bindEffectImmediate`. + */ + public get pharkasError() { + return ( + this[pharkas].templateError || + this[pharkas].immediateTemplateError || + this[pharkas].effectError + ) + } + /** + * An error has been observed in any observable applied to `bindEffect` or + * `bindEffectImmediate`. + */ + public get pharkasEffectError() { + return this[pharkas].effectError + } + /** + * An error has been observed in any observable applied to `bind`. Change detection + * has *stopped* for all `bind` bound template observables. + */ + public get pharkasTemplateStopped() { + return this[pharkas].templateError + } + /** + * An error has been observed in any observable applied to `bindImmediate`. + */ + public get pharkasImmediateTemplateError() { + return this[pharkas].immediateTemplateError + } + /** + * An error has been observed in any observable applied to `bind` or `bindImmediate`. + */ + public get pharkasTemplateError() { + return this[pharkas].templateError || this[pharkas].immediateTemplateError + } + + //#endregion constructor(private ref: ChangeDetectorRef) {} @@ -310,8 +362,11 @@ export class PharkasComponent implements OnInit, OnDestroy { this[subscription].add( observable.pipe(observeOn(animationFrameScheduler)).subscribe({ next: effect, - error: (error: any) => - console.error('Error in effect observation', error), + error: (error: any) => { + console.error('Error in effect observation', error) + this[pharkas].effectError = true + this.ref.detectChanges() + }, complete: () => { if (isDevMode()) { console.warn('Effect completed') @@ -335,8 +390,11 @@ export class PharkasComponent implements OnInit, OnDestroy { this[subscription].add( observable.subscribe({ next: effect, - error: (error: any) => - console.error('Error in effect observation', error), + error: (error: any) => { + console.error('Error in effect observation', error) + this[pharkas].effectError = true + this.ref.detectChanges() + }, complete: () => { if (isDevMode()) { console.warn('Effect completed') @@ -356,6 +414,21 @@ export class PharkasComponent implements OnInit, OnDestroy { this[subscription].add( prop.subject.subscribe({ next: () => this.ref.detectChanges(), + error: (error) => { + console.error( + `Error in immediate template binding "${prop.name}"`, + error + ) + this[pharkas].immediateTemplateError = true + this.ref.detectChanges() + }, + complete: () => { + if (isDevMode()) { + console.warn( + `Immediate template binding "${prop.name}" completed` + ) + } + }, }) ) } else { @@ -375,6 +448,8 @@ export class PharkasComponent implements OnInit, OnDestroy { }, error: (error: any) => { console.error('Error in template bindings', error) + this[pharkas].templateError = true + this.ref.detectChanges() }, complete: () => { if (isDevMode()) {