UNPKG

@kephas/angular

Version:

Provides integration capabilities with Angular.

908 lines (895 loc) 30.9 kB
import { ChangeDetectorRef, Component, ElementRef, ViewContainerRef, ViewChildren, Input, forwardRef, Injector, ɵɵdefineInjectable, ɵɵinject, INJECTOR, Injectable } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Logger, SingletonAppServiceContract, AppServiceContract, AppService, Priority, Requires, LogLevel } from '@kephas/core'; import { Notification } from '@kephas/ui'; import { __decorate, __metadata, __awaiter } from 'tslib'; import { HTTP_INTERCEPTORS, XhrFactory, HttpBackend, HttpXhrBackend, HttpClient, HttpHandler } from '@angular/common/http'; import 'reflect-metadata'; import { CommandProcessorClient, CommandError } from '@kephas/commands'; import { retry, map, catchError } from 'rxjs/operators'; import { MessageProcessorClient, MessagingError } from '@kephas/messaging'; /** * Provides base functionality for a widget. * * @export * @class WidgetBase */ class WidgetBase { /** * Creates an instance of WidgetBase. * @param {ElementRef} elementRef The element reference. * @param {ViewContainerRef} viewContainerRef The view container reference. * @memberof WidgetBase */ constructor(elementRef, viewContainerRef) { this.elementRef = elementRef; this.viewContainerRef = viewContainerRef; this._isVisible = true; this._readonly = false; const injector = viewContainerRef.injector; this.logger = injector.get(Logger); this.notification = injector.get(Notification); this.changeDetector = injector.get(ChangeDetectorRef); } /** * Gets or sets the child editors query. * * @readonly * @type {QueryList<EditorBase<any>>} * @memberof EditorBase */ get childWidgets() { return this._childWidgets; } set childWidgets(value) { if (this._childWidgets === value) { return; } const oldValue = this._childWidgets; this._childWidgets = value; this.onChildWidgetsChanged(oldValue, value); } /** * Gets or sets a value indicating whether the widget is visible. * * @readonly * @type {boolean} * @memberof WidgetBase */ get isVisible() { return this._isVisible; } set isVisible(value) { if (this._isVisible === value) { return; } this._isVisible = value; } /** * Gets or sets a value indicating whether the editor allows edits or not. * * @readonly * @type {boolean} * @memberof EditorBase */ get readonly() { return this._readonly; } set readonly(value) { if (this._readonly === value) { return; } const oldValue = this._readonly; this._readonly = value; this.onReadOnlyChanged(oldValue, value); } /** * A callback method that is invoked immediately after the * default change detector has checked the directive's * data-bound properties for the first time, * and before any of the view or content children have been checked. * It is invoked only once when the directive is instantiated. * * @memberof WidgetBase */ ngOnInit() { } /** * A callback method that is invoked immediately after * Angular has completed initialization of a component's view. * It is invoked only once when the view is instantiated. * * @memberof WidgetBase */ ngAfterViewInit() { } /** * A callback method that is invoked immediately after the * default change detector has checked data-bound properties * if at least one has changed, and before the view and content * children are checked. * * @param changes The changed properties. * @memberof WidgetBase */ ngOnChanges(changes) { } /** * A callback method that performs custom clean-up, invoked immediately * after a directive, pipe, or service instance is destroyed. */ ngOnDestroy() { } /** * When overridden in a derived class, this method is called when the read only state changes. * * @protected * @param {boolean} oldValue The old value. * @param {boolean} newValue The new value. * * @memberof WidgetBase */ onReadOnlyChanged(oldValue, newValue) { } /** * When overridden in a derived class, this method is called when the child widgets query changed. * * @protected * @param {QueryList<EditorBase<any>>} oldValue The old query. * @param {QueryList<EditorBase<any>>} newValue The new query. * * @memberof EditorBase */ onChildWidgetsChanged(oldValue, newValue) { } } WidgetBase.decorators = [ { type: Component, args: [{ template: '' },] } ]; WidgetBase.ctorParameters = () => [ { type: ElementRef }, { type: ViewContainerRef } ]; WidgetBase.propDecorators = { childWidgets: [{ type: ViewChildren, args: [WidgetBase,] }], isVisible: [{ type: Input }], readonly: [{ type: Input }] }; /** * This function provides the component as a WidgetBase, * to be able to import it over this base class instead of over its own class. * * For example, use it as @ViewChild(WidgetBase) or @ViewChildren(WidgetBase). * * @export * @param {Type<any>} componentType The component type. * @returns {Provider} The provider. */ function provideWidget(componentType) { return { provide: WidgetBase, useExisting: forwardRef(() => componentType) }; } /** * This function provides the component as a NG_VALUE_ACCESSOR. * Thus, it is possible to bind it like this: * <my-component [(ngModel)]="boundProperty"></my-component> * * @export * @param {Type<any>} componentType The component type. * @returns {Provider} The provider. */ function provideValueAccessor(componentType) { return { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => componentType), multi: true }; } /** * Provides a base implementation for value editors. * * @export * @abstract * @class ValueEditorBase * @implements {OnInit} * @implements {AfterViewInit} * @implements {ControlValueAccessor} * @template TValue The value type. */ class ValueEditorBase extends WidgetBase { /** * Creates an instance of ValueEditorBase. * * @param {ElementRef} elementRef The element reference. * @param {ViewContainerRef} viewContainerRef The view container reference. * @memberof ValueEditorBase */ constructor(elementRef, viewContainerRef) { super(elementRef, viewContainerRef); /** * Gets or sets a value indicating whether the value is changed from the change event. * * @protected * @memberof ValueEditorBase */ this.valueChangeFromEvent = false; /** * Gets or sets a value indicating whether the value is changed from the value property. * * @protected * @memberof ValueEditorBase */ this.valueChangeFromValue = false; this._onChange = (_) => { // The implementation will get overwritten by Angular in RegisterOnChange. }; this._onTouched = () => { // The implementation will get overwritten by Angular in RegisterOnTouched. }; } /** * Gets or sets the value to edit. * * @type {TValue} * @memberOf ValueEditorBase */ get value() { return this.getEditorValue(); } set value(value) { if (this._valueBeforeChange === value) { return; } this.updateEditor(value); } /** * Updates the underlying editor with the provided value. * * @protected * @param {TValue} value * @returns {boolean} * @memberof ValueEditorBase */ updateEditor(value) { if (this.valueChangeFromValue) { return false; } const prevValueChangeFromValue = this.valueChangeFromValue; this.valueChangeFromValue = true; try { const oldValue = this._valueBeforeChange; this.onValueChanging(oldValue, value); this._valueBeforeChange = value; if (!this.valueChangeFromEvent) { this.setEditorValue(value); value = this.getEditorValue(); } this.onValueChanged(oldValue, value); } catch (error) { this.logger.error(error, 'Error while updating the editor.'); throw error; } finally { this.valueChangeFromValue = prevValueChangeFromValue; } return true; } /** * Overridable method invoked when the value is about to be changed. * * @protected * @param {(TValue | undefined)} oldValue The old value. * @param {(TValue | undefined)} newValue The new value. * @memberof ValueEditorBase */ onValueChanging(oldValue, newValue) { } /** * Overridable method invoked after the value was changed. * * @protected * @param {(TValue | undefined)} oldValue The old value. * @param {(TValue | undefined)} newValue The new value. * @memberof ValueEditorBase */ onValueChanged(oldValue, newValue) { this._onChange(newValue); } /** * Callback invoked from the change event of the underlying editor. * * @protected * @param {*} e * @returns * @memberof PropertyEditorComponent */ onEditorChange(e) { if (this.valueChangeFromValue) { return; } const prevValueChangeFromEvent = this.valueChangeFromEvent; this.valueChangeFromEvent = true; try { const newValue = this.getEditorValueOnChange(e); this.value = newValue; } catch (error) { this.notification.notifyError(error); } finally { this.valueChangeFromEvent = prevValueChangeFromEvent; } } /** * Gets the underlying editor's value upon change. * * @protected * @param {*} e The change event arguments. * @returns {TValue} The widget value. * @memberof ValueEditorBase */ getEditorValueOnChange(e) { return this.getEditorValue(); } /** * Write a new value to the element. * * @param {*} obj The new value. * * @memberOf PropertyEditorComponent */ writeValue(obj) { this.value = obj; } /** * Set the function to be called when the control receives a change event. * * @param {*} fn The callback function. * * @memberOf PropertyEditorComponent */ registerOnChange(fn) { this._onChange = fn; } /** * Set the function to be called when the control receives a touch event. * * @param {*} fn The callback function. * * @memberOf PropertyEditorComponent */ registerOnTouched(fn) { this._onTouched = fn; } /** * This function is called when the control status changes to or from "DISABLED". * Depending on the value, it will enable or disable the appropriate DOM element. * * @param {boolean} isDisabled True if the state is disabled. * * @memberOf PropertyEditorComponent */ setDisabledState(isDisabled) { } } ValueEditorBase.decorators = [ { type: Component, args: [{ template: '' },] } ]; ValueEditorBase.ctorParameters = () => [ { type: ElementRef }, { type: ViewContainerRef } ]; ValueEditorBase.propDecorators = { value: [{ type: Input }] }; /** * Base class for HTTP interceptors. * * @export * @extends {AngularHttpInterceptor} */ let HttpInterceptor = class HttpInterceptor { }; HttpInterceptor = __decorate([ SingletonAppServiceContract({ allowMultiple: true, contractToken: HTTP_INTERCEPTORS }) ], HttpInterceptor); SingletonAppServiceContract()(XhrFactory); AppServiceContract()(HttpBackend); AppServiceContract()(Injector); AppServiceContract()(HttpXhrBackend); AppService({ overridePriority: Priority.Low })(HttpXhrBackend); // tslint:disable: max-classes-per-file /** * Browser implementation for an `XhrFactory`. * * @export * @class BrowserXhrFactory * @implements {XhrFactory} */ class BrowserXhrFactory { build() { return new XMLHttpRequest(); } } /** * `HttpHandler` which applies an `HttpInterceptor` to an `HttpRequest`. * * */ class HttpInterceptorHandler { constructor(next, interceptor) { this.next = next; this.interceptor = interceptor; } handle(req) { return this.interceptor.intercept(req, this.next); } } /** * An injectable `HttpHandler` that applies multiple interceptors * to a request before passing it to the given `HttpBackend`. * * The interceptors are loaded lazily from the injector, to allow * interceptors to themselves inject classes depending indirectly * on `HttpInterceptingHandler` itself. * @see `HttpInterceptor` */ class HttpInterceptingHandler { /** * Creates an instance of HttpInterceptingHandler. * @param {HttpBackend} backend * @param {Injector} injector * @memberof HttpInterceptingHandler */ constructor(backend, injector) { this.backend = backend; this.injector = injector; this.chain = null; } handle(req) { if (this.chain === null) { const interceptors = this.injector.get(HTTP_INTERCEPTORS, []); this.chain = interceptors.reduceRight((next, interceptor) => new HttpInterceptorHandler(next, interceptor), this.backend); } return this.chain.handle(req); } } HttpInterceptingHandler.ɵprov = ɵɵdefineInjectable({ factory: function HttpInterceptingHandler_Factory() { return new HttpInterceptingHandler(ɵɵinject(HttpBackend), ɵɵinject(INJECTOR)); }, token: HttpInterceptingHandler, providedIn: "root" }); HttpInterceptingHandler.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; HttpInterceptingHandler.ctorParameters = () => [ { type: HttpBackend }, { type: Injector } ]; /** * Registry for HTTP client services. * * @export * @class HttpClientAppServiceInfoRegistry */ class HttpClientAppServiceInfoRegistry { /** * Gets the providers for the HTTP client. * * @returns {((StaticClassProvider | ExistingProvider)[])} * @memberof HttpClientAppServiceInfoRegistry */ getHttpClientProviders() { return [ { provide: HttpClient, useClass: HttpClient, deps: [HttpHandler] }, { provide: HttpHandler, useClass: HttpInterceptingHandler, deps: [HttpBackend, Injector] }, { provide: HttpBackend, useClass: HttpXhrBackend, deps: [XhrFactory] }, { provide: XhrFactory, useClass: BrowserXhrFactory, deps: [] }, ]; } } /** * Helper class for registering the services with the Angular injector. * * @export */ class AngularAppServiceInfoRegistry { /** * Creates an instance of AngularAppServiceInfoRegistry. * * @param {AppServiceInfoRegistry} serviceRegistry The service registry. */ constructor(serviceRegistry) { this.serviceRegistry = serviceRegistry; Requires.HasValue(serviceRegistry, 'serviceRegistry'); } /** * Registers the application services to the Angular DI container. * * @param {...string[]} modules The modules to import to collect the service metadata. */ registerServices() { for (const serviceMetadata of this.serviceRegistry.services) { Injectable({ providedIn: 'root' })(serviceMetadata.serviceType); } } /** * Gets the providers for the root. * * @returns {StaticClassProvider[]} */ getRootProviders() { const providers = []; for (const c of this.serviceRegistry.serviceContracts) { const serviceContract = c; for (const m of serviceContract.services) { const serviceMetadata = m; providers.push({ provide: serviceContract.contractToken || serviceContract.contractType, useClass: serviceMetadata.serviceType, multi: serviceContract.allowMultiple, deps: this.getDependencies(serviceMetadata.serviceType), }); } } providers.push(...new HttpClientAppServiceInfoRegistry().getHttpClientProviders()); return providers; } getDependencies(serviceType) { let deps = Reflect.getMetadata('design:paramtypes', serviceType); if (!deps && serviceType.ctorParameters) { deps = serviceType.ctorParameters(); } return deps || []; } } /** * Gets the application settings. * * @export * @class AppSettings */ let AppSettings = class AppSettings { /** * Gets the base URL of the application. * * @readonly * @type {string} * @memberof AppSettings */ get baseUrl() { const baseQuery = document.getElementsByTagName('base'); const baseElement = baseQuery && baseQuery[0]; return (baseElement && baseElement.href) || document.baseURI || '/'; } /** * Gets the base API URL of the application. * * @readonly * @type {string} * @memberof AppSettings */ get baseApiUrl() { return `${this.baseUrl}api/`; } }; AppSettings = __decorate([ AppService({ overridePriority: Priority.Low }), SingletonAppServiceContract() ], AppSettings); /** * Provides proxied command execution over HTTP. * * @export * @class HttpCommandProcessorClient */ let HttpCommandProcessorClient = class HttpCommandProcessorClient extends CommandProcessorClient { /** * Initializes a new instance of the CommandProcessor class. * @param {Notification} notification The notification service. * @param {HttpClient} http The HTTP client. * @param {AppSettings} appSettings The application settings. */ constructor(appSettings, http, notification, logger) { super(); this.appSettings = appSettings; this.http = http; this.notification = notification; this.logger = logger; /** * Gets or sets the base route for the command execution. * * @protected * @type {string} * @memberof CommandProcessor */ this.baseRoute = 'api/cmd/'; } /** * Processes the command asynchronously. * @tparam T The command response type. * @param {string} command The command. * @param {{}} [args] Optional. The arguments. * @param {CommandClientContext} [options] Optional. Options controlling the command processing. * @returns {Observable{T}} An observable over the result. */ process(command, args, options) { const url = this.getHttpGetUrl(command, args, options); let obs = this.http.get(url, this.getHttpGetOptions(command, args, options)); if (options && options.retries) { obs = obs.pipe(retry(options.retries), map(response => this._processResponse(response, options)), catchError(error => this._processError(error, options))); } else { obs = obs.pipe(map(response => this._processResponse(response, options)), catchError(error => this._processError(error, options))); } return obs; } /** * Gets the HTTP GET URL. * * @protected * @param {string} command The command. * @param {{}} [args] Optional. The arguments. * @param {CommandClientContext} [options] Optional. Options controlling the command processing. * @returns {string} The HTTP GET URL. * @memberof CommandProcessor */ getHttpGetUrl(command, args, options) { let baseUrl = this.appSettings.baseUrl; if (!baseUrl.endsWith('/')) { baseUrl = baseUrl + '/'; } let url = `${baseUrl}${this.baseRoute}${command}/`; if (args) { url = url + '?' + Object.keys(args) .map(key => `${key}=${args[key]}`) .join('&'); } return url; } /** * Gets the HTTP GET options. By default it does not return any options. * * @protected * @param {string} command The command. * @param {{}} [args] Optional. The arguments. * @param {CommandClientContext} [options] Optional. Options controlling the command processing. * @returns {({ * headers?: HttpHeaders | { * [header: string]: string | string[]; * }; * observe?: 'body'; * params?: HttpParams | { * [param: string]: string | string[]; * }; * reportProgress?: boolean; * responseType?: 'json'; * withCredentials?: boolean; * } | undefined)} The options or undefined. * @memberof CommandProcessor */ getHttpGetOptions(command, args, options) { return undefined; } _processResponse(response, options) { if (typeof response.severity === 'string') { response.severity = LogLevel[response.severity]; } if (response.severity <= LogLevel.Error) { throw new CommandError(response.message, response); } if (response.severity === LogLevel.Warning) { this.logger.log(response.severity, null, response.message); if (!(options && (options.notifyWarnings === undefined || options.notifyWarnings))) { this.notification.notifyWarning(response); } } if (response.severity <= LogLevel.Error) { throw new Error(response.message); } return response; } _processError(error, options) { this.logger.error(error); if (!(options && (options.notifyErrors === undefined || options.notifyErrors))) { this.notification.notifyError(error); } throw error; } }; HttpCommandProcessorClient = __decorate([ AppService({ overridePriority: Priority.Low }), __metadata("design:paramtypes", [AppSettings, HttpClient, Notification, Logger]) ], HttpCommandProcessorClient); /** * Provides proxied message processing over HTTP. * * @export * @class MessageProcessor */ let HttpMessageProcessorClient = class HttpMessageProcessorClient extends MessageProcessorClient { /** * Initializes a new instance of the HttpMessageProcessor class. * @param {Notification} notification The notification service. * @param {HttpClient} http The HTTP client. * @param {AppSettings} appSettings The application settings. */ constructor(appSettings, http, notification, logger) { super(); this.appSettings = appSettings; this.http = http; this.notification = notification; this.logger = logger; /** * Gets or sets the base route for the command execution. * * @protected * @type {string} * @memberof MessageProcessor */ this.baseRoute = 'api/msg/'; } /** * Processes the message asynchronously. * @tparam T The message response type. * @param {{}} message The message. * @param {MessagingClientContext} [options] Optional. Options controlling the message processing. * @returns {Observable{T}} An observable over the result. */ process(message, options) { const url = this.getHttpPostUrl(message, options); const obs = this.http.post(url, message, this.getHttpPostOptions(message, options)); const responseObj = (options && options.retries) ? obs.pipe(retry(options.retries), map(response => this._processResponse(response, options)), catchError(error => this._processError(error, options))) : obs.pipe(map(response => this._processResponse(response, options)), catchError(error => this._processError(error, options))); return responseObj; } /** * Gets the HTTP GET URL. * * @protected * @param {{}} message The message. * @param {MessagingClientContext} [options] Optional. Options controlling the command processing. * @returns {string} The HTTP GET URL. * @memberof MessageProcessor */ getHttpPostUrl(message, options) { let baseUrl = this.appSettings.baseUrl; if (!baseUrl.endsWith('/')) { baseUrl = baseUrl + '/'; } const url = `${baseUrl}${this.baseRoute}`; return url; } /** * Gets the HTTP GET options. By default it does not return any options. * * @protected * @param {string} command The command. * @param {{}} [args] Optional. The arguments. * @param {MessagingClientContext} [options] Optional. Options controlling the command processing. * @returns {({ * headers?: HttpHeaders | { * [header: string]: string | string[]; * }; * observe?: 'body'; * params?: HttpParams | { * [param: string]: string | string[]; * }; * reportProgress?: boolean; * responseType?: 'json'; * withCredentials?: boolean; * } | undefined)} The options or undefined. * @memberof MessageProcessor */ getHttpPostOptions(message, options) { return undefined; } _processResponse(rawResponse, options) { if (rawResponse.exception) { const errorInfo = rawResponse.exception; if (typeof errorInfo.severity === 'string') { errorInfo.severity = LogLevel[errorInfo.severity]; } throw new MessagingError(errorInfo.message, errorInfo); } const response = rawResponse.message; if (typeof response.severity === 'string') { response.severity = LogLevel[response.severity]; } if (response.severity <= LogLevel.Error) { throw new MessagingError(response.message, response); } if (response.severity === LogLevel.Warning) { this.logger.log(response.severity, null, response.message); if (!(options && (options.notifyWarnings === undefined || options.notifyWarnings))) { this.notification.notifyWarning(response); } } if (response.severity <= LogLevel.Error) { throw new MessagingError(response.message, response); } return response; } _processError(error, options) { this.logger.error(error); if (!(options && (options.notifyErrors === undefined || options.notifyErrors))) { this.notification.notifyError(error); } throw error; } }; HttpMessageProcessorClient = __decorate([ AppService({ overridePriority: Priority.Low }), __metadata("design:paramtypes", [AppSettings, HttpClient, Notification, Logger]) ], HttpMessageProcessorClient); var Configuration_1; // https://stackoverflow.com/questions/50222998/error-encountered-in-metadata-generated-for-exported-symbol-when-constructing-an // @dynamic /** * The configuration service. * * @export * @class Configuration * @implements {AsyncInitializable} */ let Configuration = Configuration_1 = class Configuration { /** * Ensures that the configuration file is initialized. * * @param {{ http: HttpClient, configurationFileUrl?: string }} context The context containing initialization data. * @returns {Promise<void>} * @memberof Configuration */ initializeAsync(context) { return __awaiter(this, void 0, void 0, function* () { if (Configuration_1.configurationFile) { return; } const response = yield context.http.get(context.configurationFileUrl ? context.configurationFileUrl : Configuration_1.configurationFileUrl).toPromise(); Configuration_1.configurationFile = response || {}; }); } /** * Gets the configuration settings for the indicated section name. * * @template T The settings type. * @param {string} sectionName The section name. * @returns {T} The settings. * @memberof Configuration */ getSettings(settingsType) { if (!Configuration_1.configurationFile) { throw new Error('The configuration manager must be initialized prior to requesting settings from it.'); } let sectionName = settingsType.name; const ending = 'Settings'; if (sectionName.endsWith(ending)) { sectionName = sectionName.substr(0, sectionName.length - ending.length); } sectionName = sectionName[0].toLowerCase() + sectionName.substr(1, sectionName.length - 1); return Configuration_1.configurationFile[sectionName]; } }; Configuration.configurationFileUrl = '/app/configuration.json'; Configuration = Configuration_1 = __decorate([ AppService({ overridePriority: Priority.Low }), SingletonAppServiceContract() ], Configuration); /* * Public API Surface of @kephas/angular */ /** * Generated bundle index. Do not edit. */ export { AngularAppServiceInfoRegistry, AppSettings, BrowserXhrFactory, Configuration, HttpClientAppServiceInfoRegistry, HttpCommandProcessorClient, HttpInterceptingHandler, HttpInterceptor, HttpInterceptorHandler, HttpMessageProcessorClient, ValueEditorBase, WidgetBase, provideValueAccessor, provideWidget }; //# sourceMappingURL=kephas-angular.js.map