UNPKG

@rxap/remote-method

Version:

This package provides abstractions for defining and executing remote methods in Angular applications. It includes features such as automatic refreshing, proxying, and error handling. It offers a structured way to manage remote calls and their dependencies

673 lines (664 loc) 28.2 kB
import * as i1 from '@rxap/remote-method'; import { RxapRemoteMethodError, RemoteMethodLoader } from '@rxap/remote-method'; import * as i0 from '@angular/core'; import { InjectionToken, isDevMode, TemplateRef, INJECTOR, ViewContainerRef, ChangeDetectorRef, IterableDiffers, NgZone, Directive, Inject, Optional, Self, Input, EventEmitter, Output, Injector, HostListener } from '@angular/core'; import { take, tap } from 'rxjs/operators'; import { __decorate, __metadata } from 'tslib'; import { Deprecated, coerceBoolean } from '@rxap/utilities'; import { ToggleSubject } from '@rxap/rxjs'; class RxapRemoteMethodDirectiveError extends RxapRemoteMethodError { constructor(message, code, scope) { super(message, code ?? '', scope); } } const RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN = new InjectionToken('rxap/remote-method/directive/token'); class RemoteMethodTemplateCollectionDirectiveContext { constructor($implicit, index, count) { this.$implicit = $implicit; this.index = index; this.count = count; } get first() { return this.index === 0; } get last() { return this.index === this.count - 1; } get even() { return this.index % 2 === 0; } get odd() { return !this.even; } } function getTypeName(type) { return type['name'] || typeof type; } class RecordViewTuple { constructor(record, view) { this.record = record; this.view = view; } } class RemoteMethodTemplateCollectionDirective { constructor(template, remoteMethodLoader, injector, viewContainerRef, cdr, differs, zone, remoteMethodToken) { this.template = template; this.remoteMethodLoader = remoteMethodLoader; this.injector = injector; this.viewContainerRef = viewContainerRef; this.cdr = cdr; this.differs = differs; this.zone = zone; this.remoteMethodToken = remoteMethodToken; this.withoutParameters = false; this._differ = null; this._dirty = true; /** * Indicates that the remote method returned a empty collection * * true - is empty * false - is NOT empty * null - unknown * * @private */ this._empty = null; /** * Holds the error that call of the remote method throws * * @private */ this._error = null; /** * Holds the empty template view ref. * * Is used to determine if a empty template is added to the * view. And used to destruct the empty template if the remote method * changes from empty to not empty. * * @private */ this._emptyTemplateViewRef = null; /** * Holds the error template view ref. * * Is used to determine if a error template is added to the * view. And used to destruct the error template if the remote method * is called again with an error. * * @private */ this._errorTemplateViewRef = null; /** * Holds the data that should be displayed * @private */ this._data = null; if (this.remoteMethodToken) { this._remoteMethodOrIdOrToken = this.remoteMethodToken; } } get trackBy() { return this._trackByFn; } /** * A function that defines how to track changes for items in the iterable. * * When items are added, moved, or removed in the iterable, * the directive must re-render the appropriate DOM nodes. * To minimize churn in the DOM, only nodes that have changed * are re-rendered. * * By default, the change detector assumes that * the object instance identifies the node in the iterable. * When this function is supplied, the directive uses * the result of calling this function to identify the item node, * rather than the identity of the object itself. * * The function receives two inputs, * the iteration index and the node object ID. */ set trackBy(fn) { if (isDevMode() && fn != null && typeof fn !== 'function') { // TODO(vicb): use a log service once there is a public one available if (console && console.warn) { console.warn(`trackBy must be a function, but received ${JSON.stringify(fn)}. ` + `See https://angular.io/api/common/NgForOf#change-propagation for more information.`); } } this._trackByFn = fn; } // eslint-disable-next-line @angular-eslint/no-input-rename set remoteMethodOrIdOrToken(value) { if (value) { this._remoteMethodOrIdOrToken = value; } } set data(data) { this._data = data; this._dirty = true; } /** * Asserts the correct type of the context for the template that `NgForOf` will render. * * The presence of this method is a signal to the Ivy template type-check compiler that the * `NgForOf` structural directive renders its template with a specific context type. */ static ngTemplateContextGuard(dir, ctx) { return true; } ngOnChanges(changes) { const parametersChanges = changes['parameters']; if (parametersChanges) { this.call(parametersChanges.currentValue); } } ngOnInit() { if (this.withoutParameters) { this.call(); } } call(parameters) { this.zone.onStable.pipe(take(1), tap(() => { this.zone.run(() => { this.remoteMethodLoader .call$(this._remoteMethodOrIdOrToken, parameters, undefined, this.injector) .then(response => { this._empty = response.length === 0; this._data = response; this.cdr.detectChanges(); }) .catch(error => { this._error = error; this.cdr.detectChanges(); }); }); })).subscribe(); } /** * Applies the changes when needed. */ ngDoCheck() { if (this._error !== null) { this.viewContainerRef.clear(); this._differ = null; if (this.errorTemplate) { this._errorTemplateViewRef = this.viewContainerRef.createEmbeddedView(this.errorTemplate, { $implicit: this._error, message: this._error.message, name: this._error.name, }); } } else if (this._empty === true) { this.viewContainerRef.clear(); this._differ = null; // attaches the empty template if the data source is empty if (this.emptyTemplate) { this._emptyTemplateViewRef = this.viewContainerRef.createEmbeddedView(this.emptyTemplate); } } else if (this._empty === false) { // detach and destroy the empty template if the data source is not // empty any more if (this._emptyTemplateViewRef) { this._emptyTemplateViewRef.detach(); this._emptyTemplateViewRef.destroy(); this._errorTemplateViewRef = null; } if (this._errorTemplateViewRef) { this._errorTemplateViewRef.detach(); this._errorTemplateViewRef.destroy(); this._errorTemplateViewRef = null; } if (this._data) { this._dirty = false; // React on ngForOf changes only once all inputs have been initialized const value = this._data; if (!this._differ && value) { try { this._differ = this.differs.find(value).create(this.trackBy); } catch { throw new Error(`Cannot find a differ supporting object '${value}' of type '${getTypeName(value)}'. NgFor only supports binding to Iterables such as Arrays.`); } } } if (this._differ) { const changes = this._differ.diff(this._data); if (changes) { this.applyChanges(changes); } } } } applyChanges(changes) { const insertTuples = []; changes.forEachOperation((item, adjustedPreviousIndex, currentIndex) => { if (item.previousIndex == null) { // NgForOf is never "null" or "undefined" here because the differ detected // that a new item needs to be inserted from the iterable. This implies that // there is an iterable value for "_ngForOf". const view = this.viewContainerRef.createEmbeddedView(this.template, new RemoteMethodTemplateCollectionDirectiveContext(null, -1, -1), currentIndex === null ? undefined : currentIndex); const tuple = new RecordViewTuple(item, view); insertTuples.push(tuple); } else if (currentIndex == null) { this.viewContainerRef.remove(adjustedPreviousIndex === null ? undefined : adjustedPreviousIndex); } else if (adjustedPreviousIndex !== null) { const view = this.viewContainerRef.get(adjustedPreviousIndex); this.viewContainerRef.move(view, currentIndex); const tuple = new RecordViewTuple(item, view); insertTuples.push(tuple); } }); for (let i = 0; i < insertTuples.length; i++) { this.perViewChange(insertTuples[i].view, insertTuples[i].record); } for (let i = 0, ilen = this.viewContainerRef.length; i < ilen; i++) { const viewRef = this.viewContainerRef.get(i); viewRef.context.index = i; viewRef.context.count = ilen; } changes.forEachIdentityChange((record) => { const viewRef = this.viewContainerRef.get(record.currentIndex); viewRef.context.$implicit = record.item; }); } perViewChange(view, record) { view.context.$implicit = record.item; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: RemoteMethodTemplateCollectionDirective, deps: [{ token: TemplateRef }, { token: RemoteMethodLoader }, { token: INJECTOR }, { token: ViewContainerRef }, { token: ChangeDetectorRef }, { token: IterableDiffers }, { token: NgZone }, { token: RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.1", type: RemoteMethodTemplateCollectionDirective, isStandalone: true, selector: "[rxapRemoteMethodCollection]", inputs: { parameters: ["rxapRemoteMethodCollectionParameters", "parameters"], emptyTemplate: ["rxapRemoteMethodCollectionEmpty", "emptyTemplate"], errorTemplate: ["rxapRemoteMethodCollectionError", "errorTemplate"], withoutParameters: ["rxapRemoteMethodCollectionWithoutParameters", "withoutParameters"], trackBy: ["rxapRemoteMethodCollectionTrackBy", "trackBy"], remoteMethodOrIdOrToken: ["rxapRemoteMethodCollectionCall", "remoteMethodOrIdOrToken"] }, exportAs: ["rxapRemoteMethodCollection"], usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: RemoteMethodTemplateCollectionDirective, decorators: [{ type: Directive, args: [{ selector: '[rxapRemoteMethodCollection]', exportAs: 'rxapRemoteMethodCollection', standalone: true, }] }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{ type: Inject, args: [TemplateRef] }] }, { type: i1.RemoteMethodLoader, decorators: [{ type: Inject, args: [RemoteMethodLoader] }] }, { type: i0.Injector, decorators: [{ type: Inject, args: [INJECTOR] }] }, { type: i0.ViewContainerRef, decorators: [{ type: Inject, args: [ViewContainerRef] }] }, { type: i0.ChangeDetectorRef, decorators: [{ type: Inject, args: [ChangeDetectorRef] }] }, { type: i0.IterableDiffers, decorators: [{ type: Inject, args: [IterableDiffers] }] }, { type: i0.NgZone, decorators: [{ type: Inject, args: [NgZone] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Self }, { type: Inject, args: [RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN] }] }], propDecorators: { parameters: [{ type: Input, args: ['rxapRemoteMethodCollectionParameters'] }], emptyTemplate: [{ type: Input, args: ['rxapRemoteMethodCollectionEmpty'] }], errorTemplate: [{ type: Input, args: ['rxapRemoteMethodCollectionError'] }], withoutParameters: [{ type: Input, args: ['rxapRemoteMethodCollectionWithoutParameters'] }], trackBy: [{ type: Input, args: ['rxapRemoteMethodCollectionTrackBy'] }], remoteMethodOrIdOrToken: [{ type: Input, args: ['rxapRemoteMethodCollectionCall'] }] } }); class RemoteMethodTemplateDirective { constructor(template, remoteMethodLoader, injector, viewContainerRef, cdr, remoteMethodToken) { this.template = template; this.remoteMethodLoader = remoteMethodLoader; this.injector = injector; this.viewContainerRef = viewContainerRef; this.cdr = cdr; this.remoteMethodToken = remoteMethodToken; // TODO : remove template context Guard // the template context Guard must be defined with a concrete context type // else the context guard is resolved to any // For each child directive that should have a template context guard // the template context guard must be defined // eslint-disable-next-line @angular-eslint/no-output-rename this.executed$ = new EventEmitter(); // eslint-disable-next-line @angular-eslint/no-output-rename this.failure$ = new EventEmitter(); // eslint-disable-next-line @angular-eslint/no-output-rename this.successful$ = new EventEmitter(); /** * true - a remote method call is in progress */ this.executing$ = new ToggleSubject(); this.withoutParameters = false; this.embedded = new EventEmitter(); if (this.remoteMethodToken) { this._remoteMethodOrIdOrToken = this.remoteMethodToken; } } // eslint-disable-next-line @angular-eslint/no-input-rename set remoteMethodOrIdOrToken(value) { if (value) { this._remoteMethodOrIdOrToken = value; } } // !! add generation of concrete template context guard to openapi schematics !! static ngTemplateContextGuard(dir, ctx) { return true; } ngOnChanges(changes) { const parametersChanges = changes['parameters']; if (parametersChanges) { this.execute(parametersChanges.currentValue); } } ngOnInit() { this.renderLoadingTemplate(); if (this.withoutParameters) { this.execute(); } } async execute(parameters = this.parameters) { this.executing$.enable(); this.renderLoadingTemplate(); try { const result = await this.remoteMethodLoader.call$(this._remoteMethodOrIdOrToken, parameters, this.metadata, this.injector); this.executed(result); this.renderTemplate(result); this.successful(result); } catch (error) { if (isDevMode()) { console.error(`Remote method directive execution failed:`, error.message); } this.failure(error); } finally { this.executing$.disable(); } } /** * @deprecated removed * @protected */ call(parameters) { this.execute(parameters) .catch(error => console.error('Remote method template rendering failed: ' + error.message)); } setParameter(parameterKey, value) { this.updateParameters({ [parameterKey]: value }); } updateParameters(parameters) { this.parameters = { ...(this.parameters ?? {}), ...parameters }; } executed(result) { this.executed$.emit(result); } failure(error) { this.failure$.emit(error); } successful(result) { this.successful$.emit(result); } renderTemplate(result) { this.viewContainerRef.clear(); try { this.viewContainerRef.createEmbeddedView(this.template, { $implicit: result }); } catch (error) { if (this.errorTemplate) { this.viewContainerRef.createEmbeddedView(this.errorTemplate, { $implicit: error, message: error.message, }); } console.error(error.message); } this.embedded.emit(); this.cdr.detectChanges(); } renderLoadingTemplate() { if (this.loadingTemplate) { this.viewContainerRef.clear(); this.viewContainerRef.createEmbeddedView(this.loadingTemplate); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: RemoteMethodTemplateDirective, deps: [{ token: TemplateRef }, { token: RemoteMethodLoader }, { token: INJECTOR }, { token: ViewContainerRef }, { token: ChangeDetectorRef }, { token: RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.1", type: RemoteMethodTemplateDirective, isStandalone: true, selector: "[rxapRemoteMethod]", inputs: { metadata: ["rxapRemoteMethodMetadata", "metadata"], parameters: ["rxapRemoteMethodParameters", "parameters"], errorTemplate: ["rxapRemoteMethodError", "errorTemplate"], loadingTemplate: ["rxapRemoteMethodLoading", "loadingTemplate"], withoutParameters: ["rxapRemoteMethodWithoutParameters", "withoutParameters"], remoteMethodOrIdOrToken: ["rxapRemoteMethodCall", "remoteMethodOrIdOrToken"] }, outputs: { executed$: "executed", failure$: "failure", successful$: "successful", embedded: "embedded" }, exportAs: ["rxapRemoteMethod"], usesOnChanges: true, ngImport: i0 }); } } __decorate([ Deprecated('removed'), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], RemoteMethodTemplateDirective.prototype, "call", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: RemoteMethodTemplateDirective, decorators: [{ type: Directive, args: [{ selector: '[rxapRemoteMethod]', exportAs: 'rxapRemoteMethod', standalone: true, }] }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{ type: Inject, args: [TemplateRef] }] }, { type: i1.RemoteMethodLoader, decorators: [{ type: Inject, args: [RemoteMethodLoader] }] }, { type: i0.Injector, decorators: [{ type: Inject, args: [INJECTOR] }] }, { type: i0.ViewContainerRef, decorators: [{ type: Inject, args: [ViewContainerRef] }] }, { type: i0.ChangeDetectorRef, decorators: [{ type: Inject, args: [ChangeDetectorRef] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Self }, { type: Inject, args: [RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN] }] }], propDecorators: { executed$: [{ type: Output, args: ['executed'] }], failure$: [{ type: Output, args: ['failure'] }], successful$: [{ type: Output, args: ['successful'] }], metadata: [{ type: Input, args: ['rxapRemoteMethodMetadata'] }], parameters: [{ type: Input, args: ['rxapRemoteMethodParameters'] }], errorTemplate: [{ type: Input, args: ['rxapRemoteMethodError'] }], loadingTemplate: [{ type: Input, args: ['rxapRemoteMethodLoading'] }], withoutParameters: [{ type: Input, args: ['rxapRemoteMethodWithoutParameters'] }], embedded: [{ type: Output }], remoteMethodOrIdOrToken: [{ type: Input, args: ['rxapRemoteMethodCall'] }], call: [] } }); class RemoteMethodDirective { constructor(remoteMethodLoader, injector = Injector.NULL, remoteMethodToken) { this.remoteMethodLoader = remoteMethodLoader; this.injector = injector; this.remoteMethodToken = remoteMethodToken; // eslint-disable-next-line @angular-eslint/no-output-rename this.executed$ = new EventEmitter(); // eslint-disable-next-line @angular-eslint/no-output-rename this.failure$ = new EventEmitter(); // eslint-disable-next-line @angular-eslint/no-output-rename this.successful$ = new EventEmitter(); this.immediately = false; /** * true - a remote method call is in progress */ this.executing$ = new ToggleSubject(); this._hasConfirmDirective = false; if (this.remoteMethodToken) { this._remoteMethodOrIdOrToken = this.remoteMethodToken; } } set remoteMethodOrIdOrToken(value) { if (value) { this._remoteMethodOrIdOrToken = value; } } // eslint-disable-next-line @angular-eslint/no-input-rename set hasConfirmDirective(value) { this._hasConfirmDirective = coerceBoolean(value); } ngOnInit() { if (this.immediately) { this.execute(); } } onConfirmed() { this.execute(); } onClick() { if (!this._hasConfirmDirective) { this.execute(); } else { console.debug('skip remote method call. Wait for confirmation.'); } } async execute() { this.executing$.enable(); try { const result = await this.remoteMethodLoader.call$(this._remoteMethodOrIdOrToken, this.parameters, this.metadata, this.injector); this.executed(result); this.successful(result); } catch (error) { if (isDevMode()) { console.error(`Remote method directive execution failed:`, error.message); } this.failure(error); } finally { this.executing$.disable(); } } setParameter(parameterKey, value) { this.updateParameters({ [parameterKey]: value }); } updateParameters(parameters) { this.parameters = { ...(this.parameters ?? {}), ...parameters }; } executed(result) { this.executed$.emit(result); } failure(error) { this.failure$.emit(error); } successful(result) { this.successful$.emit(result); } /** * @deprecated removed * @protected */ async call() { this.executing$.enable(); const result = await this.remoteMethodLoader.call$(this._remoteMethodOrIdOrToken, this.parameters, this.metadata, this.injector); this.executing$.disable(); return result; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: RemoteMethodDirective, deps: [{ token: RemoteMethodLoader }, { token: INJECTOR }, { token: RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.1", type: RemoteMethodDirective, isStandalone: true, selector: "[rxapRemoteMethod]", inputs: { parameters: "parameters", metadata: "metadata", immediately: "immediately", remoteMethodOrIdOrToken: ["rxapRemoteMethod", "remoteMethodOrIdOrToken"], hasConfirmDirective: ["rxapConfirm", "hasConfirmDirective"] }, outputs: { executed$: "executed", failure$: "failure", successful$: "successful" }, host: { listeners: { "confirmed": "onConfirmed()", "click": "onClick()" } }, exportAs: ["rxapRemoteMethod"], ngImport: i0 }); } } __decorate([ Deprecated('removed'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], RemoteMethodDirective.prototype, "call", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: RemoteMethodDirective, decorators: [{ type: Directive, args: [{ selector: '[rxapRemoteMethod]', exportAs: 'rxapRemoteMethod', standalone: true, }] }], ctorParameters: () => [{ type: i1.RemoteMethodLoader, decorators: [{ type: Inject, args: [RemoteMethodLoader] }] }, { type: i0.Injector, decorators: [{ type: Inject, args: [INJECTOR] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Self }, { type: Inject, args: [RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN] }] }], propDecorators: { parameters: [{ type: Input }], metadata: [{ type: Input }], executed$: [{ type: Output, args: ['executed'] }], failure$: [{ type: Output, args: ['failure'] }], successful$: [{ type: Output, args: ['successful'] }], immediately: [{ type: Input }], remoteMethodOrIdOrToken: [{ type: Input, args: ['rxapRemoteMethod'] }], hasConfirmDirective: [{ type: Input, args: ['rxapConfirm'] }], onConfirmed: [{ type: HostListener, args: ['confirmed'] }], onClick: [{ type: HostListener, args: ['click'] }], call: [] } }); // region // endregion /** * Generated bundle index. Do not edit. */ export { RXAP_REMOTE_METHOD_DIRECTIVE_TOKEN, RemoteMethodDirective, RemoteMethodTemplateCollectionDirective, RemoteMethodTemplateCollectionDirectiveContext, RemoteMethodTemplateDirective, RxapRemoteMethodDirectiveError }; //# sourceMappingURL=rxap-remote-method-directive.mjs.map