@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
JavaScript
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