Hey everybody!
A few years back I tried to create a separate Angular client that was able to consume ASPNETZERO's public API's (non-authorized): [Reduced Angular Client #4596](https://support.aspnetzero.com/QA/Questions/4596/Reduced-Angular-Client)
While this approach did the job back then I never was too happy with it as a lot of not needed resources remained.
A few days ago I decided to try another approach: starting with a new Angular application and only add what is necessary to make it work with ASPNETZERO's Core Services.
Today, I want to share with you my results (see next comment)!
I hope it is useful to some of you!
Cheers!
6 Answer(s)
-
0
Minimalist ASPNETZERO Angular client
This project was generated with Angular CLI version 14.0.4.
This project is a minimalist ASPNETZERO Angular client. It implements the very minimal requirements to make use of the ASPNETZERO Core Services (ASPNETZERO 11.2.1).
Features
- Access to ASPNETZERO Core Service (public/non-authorized API only)
- NgxSpinner (package ngx-spinner)
- Localization
- SignalR (public/non-authorized endpoint only)
Access to ASPNETZERO Core Services
This documentation shows how to create a minimalist ASPNETZERO Angular client that can consume ASPNETZERO Core Services (public/non-authorized API only).
Add package 'nswag'
Add package:
npm install --save-dev nswag
Copy
./nswag/*
from original ASPNETZERO Angular client.In
./nswag/service.config.nswag
updatetypeScriptVersion
andrxJsVersion
according to./project.json
.Go to
./nswag
and run./resfresh.bat
(ASPNETZERO Core Services must be up and running).This creates
./src/shared/service-proxies/service-proxies.ts
.Copy
./src/shared/service-proxies/service-proxy.module.ts
from original ASPNETZERO Angular client.
Remove everything irrelevant:import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { AbpHttpInterceptor } from 'abp-ng2-module'; import * as ApiServiceProxies from './service-proxies'; @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AbpHttpInterceptor, multi: true }, ApiServiceProxies.MyServiceProxy, ], }) export class ServiceProxyModule {}
As we can see
ServiceProxyModule
requires packageabp-ng2-module
.Add package 'abp-ng2-module'
Add packages:
npm install --save luxon
npm install --save-dev @types/luxon
npm install --save --legacy-peer-deps abp-web-resources
npm install --save --legacy-peer-deps abp-ng2-module
Adding
@types
to./tsconfig.*.json
:
see: angular.io / guide / typescript-configuration / installable-typings-filesIn
./tsconfig.app.json
add"luxon"
tocompilerOptions.types
:"compilerOptions": { // ..., "types": [ "luxon" ] }
In
./tsconfig.spec.json
add"luxon"
tocompilerOptions.types
:"compilerOptions": { // ..., "types": [ // ..., "luxon" ] }
Adding
"skipLibCheck": true
to./tsconfig.compilerOptions
:
This is necessary to prevent build errors caused byabp-ng2-module
not being able to find references toabp-web-resources
.{ "compilerOptions": { // ..., "skipLibCheck": true } }
Add
./src/typings.d.ts
with the following content:
see: typescriptlang.org / docs / handbook / declaration-files / publishing / red-flags///<reference path="../node_modules/abp-web-resources/Abp/Framework/scripts/abp.d.ts"/>
Adding
abp-web-resources
to./src/assets/
:
Copy./src/assets/abp-web-resources/abp.js
from original ASPNETZERO Angular client.Adding
"src/assets/abp-web-resources/abp.js"
to./angular.json
tobuild.options
andtest.options
:"scripts": [ "src/assets/abp-web-resources/abp.js" ]
Additional Resources and AppModule
Copy the following files from original ASPNETZERO Angular client:
./src/app-pre-bootstrap.ts
./src/assets/appconfig.json
./src/assets/appconfig.production.json
./src/shared/app-consts.ts
(original name:AppConsts.ts
)./src/shared/helpers/xml-http-request-helper.ts
(original name:XmlHttpRequestHelper.ts
)
The content of
./src/app-pre-bootstrap.ts
is the following:import { environment } from './environments/environment'; import { AppConsts } from './shared/app-consts'; import { XmlHttpRequestHelper } from './shared/helpers/xml-http-request-helper'; export class AppPreBootstrap { static run(appRootUrl: string, callback: () => void, resolve: any, reject: any): void { AppPreBootstrap.getApplicationConfig(appRootUrl, () => { callback(); }); } private static getApplicationConfig(appRootUrl: string, callback: () => void) { let type = 'GET'; let url = appRootUrl + 'assets/' + environment.appConfig; let customHeaders = [{ name: 'Abp.TenantId', value: abp.multiTenancy.getTenantIdCookie() + '' }]; XmlHttpRequestHelper.ajax(type, url, customHeaders, null, (result: any) => { AppConsts.localeMappings = result.localeMappings; AppConsts.appBaseUrlFormat = result.appBaseUrl; AppConsts.remoteServiceBaseUrlFormat = result.remoteServiceBaseUrl; AppConsts.appBaseUrl = result.appBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl + '.', ''); AppConsts.remoteServiceBaseUrl = result.remoteServiceBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl + '.', ''); callback(); }); } }
Modify the following files:
./src/environments/environment.prod.ts
export const environment = { production: true, appConfig: 'appconfig.production.json' };
./src/environments/environment.ts
export const environment = { production: false, appConfig: 'appconfig.json' };
Modify
./src/app/app.module.ts
:import { PlatformLocation } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppPreBootstrap } from 'src/app-pre-bootstrap'; import { AppConsts } from 'src/shared/app-consts'; import { API_BASE_URL } from 'src/shared/service-proxies/service-proxies'; import { ServiceProxyModule } from 'src/shared/service-proxies/service-proxy.module'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; export function appInitializerFactory(platformLocation: PlatformLocation) { return () => { return new Promise<boolean>((resolve, reject) => { AppConsts.appBaseHref = getBaseHref(platformLocation); let appBaseUrl = `${getDocumentOrigin()}${AppConsts.appBaseHref}`; AppPreBootstrap.run( appBaseUrl, () => { resolve(true); }, resolve, reject ); }); }; } function getDocumentOrigin() { const docloc = document.location; return docloc.origin ?? `${docloc.protocol}//${docloc.hostname}${docloc.port ? ':' + docloc.port : ''}` } export function getRemoteServiceBaseUrl(): string { return AppConsts.remoteServiceBaseUrl; } export function getBaseHref(platformLocation: PlatformLocation) { return platformLocation.getBaseHrefFromDOM() ?? '/'; } @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, ServiceProxyModule, HttpClientModule, ], providers: [ { provide: API_BASE_URL, useFactory: getRemoteServiceBaseUrl }, { provide: APP_INITIALIZER, useFactory: appInitializerFactory, deps: [PlatformLocation], multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
-
0
NgxSpinner
This documentation shows how to make use of package ngx-spinner.
Add package:
npm install --save --legacy-peer-deps ngx-spinner
Adapt
@NgModule
decorator in./src/app/app.module.ts
as follows:@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, ServiceProxyModule, HttpClientModule, NgxSpinnerModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ { provide: API_BASE_URL, useFactory: getRemoteServiceBaseUrl }, { provide: APP_INITIALIZER, useFactory: appInitializerFactory, deps: [Injector, PlatformLocation], multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
Adapt
appInitializerFactory()
in./src/app/app.module.ts
as follows:export function appInitializerFactory(injector: Injector, platformLocation: PlatformLocation) { return () => { const spinnerService = injector.get(NgxSpinnerService); spinnerService.show(); return new Promise<boolean>((resolve, reject) => { AppConsts.appBaseHref = getBaseHref(platformLocation); let appBaseUrl = `${getDocumentOrigin()}${AppConsts.appBaseHref}`; AppPreBootstrap.run( appBaseUrl, () => { spinnerService.hide(); resolve(true); }, resolve, reject ); }); }; }
Add stylings to
./angular.json
:
Note: Which css file to import depends on the type of animation you want to use."styles": [ "node_modules/ngx-spinner/animations/ball-clip-rotate.css" ],
DONE
That's it! Now we can make use of NgxSpinner in our minimalist ASPNETZERO Angular client!
-
0
Localization
This documentation shows how to make use of ASPNETZERO's localization capabilities.
Add packages:
npm install --save --legacy-peer-deps lodash-es
npm install --save-dev --legacy-peer-deps @types/lodash-es
Edit
./tsconfig.json
to contain the followingcompilerOptions
:"strict": false, "paths": { "@shared/*": ["./src/shared/*"] }
Copy
./src/shared/services/locale-mappings.services.ts
from original ASPNETZERO Angular client (original location is./src/shared/
).Copy
./src/shared/pipes/localize.pipe.ts
from original ASPNETZERO Angular client (original location is./src/shared/common/pipes/
).Add
./src/shared/pipes/pipes.module.ts
with the following content:import { NgModule } from '@angular/core'; import { LocalizePipe } from "./localize.pipe"; @NgModule({ declarations: [ LocalizePipe, ], exports: [ LocalizePipe, ] }) export class PipesModule {}
AbpModule
Add the following functions to
./src/app/app.module.ts
:export function convertAbpLocaleToAngularLocale(locale: string): string { return new LocaleMappingService().map('angular', locale); } export function getCurrentLanguage(): string { return convertAbpLocaleToAngularLocale(abp.localization.currentLanguage.name); } function registerLocales( resolve: (value?: boolean | Promise<boolean>) => void, reject: any, spinnerService: NgxSpinnerService ) { if (abp.localization.currentLanguage.name && abp.localization.currentLanguage.name !== 'en-US') { let angularLocale = convertAbpLocaleToAngularLocale(abp.localization.currentLanguage.name); import(`/node_modules/@angular/common/locales/${angularLocale}.mjs`).then((module) => { registerLocaleData(module.default); spinnerService.hide(); resolve(true); }, reject); } else { spinnerService.hide(); resolve(true); } }
Adapt
appInitializerFactory()
in./src/app/app.module.ts
as follows:export function appInitializerFactory(injector: Injector, platformLocation: PlatformLocation) { return () => { const spinnerService = injector.get(NgxSpinnerService); spinnerService.show(); return new Promise<boolean>((resolve, reject) => { AppConsts.appBaseHref = getBaseHref(platformLocation); let appBaseUrl = `${getDocumentOrigin()}${AppConsts.appBaseHref}`; AppPreBootstrap.run( appBaseUrl, () => { registerLocales(resolve, reject, spinnerService); resolve(true); }, resolve, reject ); }); }; }
Adapt
@NgModule
decorator in./src/app/app.module.ts
as follows:@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, ServiceProxyModule, HttpClientModule, NgxSpinnerModule, PipesModule, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ { provide: API_BASE_URL, useFactory: getRemoteServiceBaseUrl }, { provide: APP_INITIALIZER, useFactory: appInitializerFactory, deps: [Injector, PlatformLocation], multi: true }, { provide: LOCALE_ID, useFactory: getCurrentLanguage } ], bootstrap: [AppComponent] }) export class AppModule { }
Adapt
./src/app-pre-bootstrap.ts
as follows:import { environment } from './environments/environment'; import { AppConsts } from './shared/app-consts'; import { XmlHttpRequestHelper } from './shared/helpers/xml-http-request-helper'; import { merge as _merge } from 'lodash-es'; import { LocaleMappingService } from '@shared/services/locale-mapping.service'; import { DateTime, Settings } from 'luxon'; export class AppPreBootstrap { static run(appRootUrl: string, callback: () => void, resolve: any, reject: any): void { AppPreBootstrap.getApplicationConfig(appRootUrl, () => { AppPreBootstrap.getUserConfiguration(callback); }); } private static getApplicationConfig(appRootUrl: string, callback: () => void) { let type = 'GET'; let url = appRootUrl + 'assets/' + environment.appConfig; let customHeaders = [{ name: 'Abp.TenantId', value: abp.multiTenancy.getTenantIdCookie() + '' }]; XmlHttpRequestHelper.ajax(type, url, customHeaders, null, (result: any) => { AppConsts.localeMappings = result.localeMappings; AppConsts.appBaseUrlFormat = result.appBaseUrl; AppConsts.remoteServiceBaseUrlFormat = result.remoteServiceBaseUrl; AppConsts.appBaseUrl = result.appBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl + '.', ''); AppConsts.remoteServiceBaseUrl = result.remoteServiceBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl + '.', ''); callback(); }); } private static getCurrentClockProvider(currentProviderName: string): abp.timing.IClockProvider { if (currentProviderName === 'unspecifiedClockProvider') { return abp.timing.unspecifiedClockProvider; } if (currentProviderName === 'utcClockProvider') { return abp.timing.utcClockProvider; } return abp.timing.localClockProvider; } private static getRequestHeadersWithDefaultValues() { const cookieLangValue = abp.utils.getCookieValue('Abp.Localization.CultureName'); let requestHeaders = { '.AspNetCore.Culture': 'c=' + cookieLangValue + '|uic=' + cookieLangValue, [abp.multiTenancy.tenantIdCookieName]: abp.multiTenancy.getTenantIdCookie(), }; if (!cookieLangValue) { delete requestHeaders['.AspNetCore.Culture']; } return requestHeaders; } private static getUserConfiguration(callback: () => void): any { const token = abp.auth.getToken(); let requestHeaders = AppPreBootstrap.getRequestHeadersWithDefaultValues(); if (token) { requestHeaders['Authorization'] = 'Bearer ' + token; } return XmlHttpRequestHelper.ajax( 'GET', AppConsts.remoteServiceBaseUrl + '/AbpUserConfiguration/GetAll', requestHeaders, null, (response) => { let result = response.result; _merge(abp, result); abp.clock.provider = this.getCurrentClockProvider(result.clock.provider); AppPreBootstrap.configureLuxon(); abp.event.trigger('abp.dynamicScriptsInitialized'); AppConsts.recaptchaSiteKey = abp.setting.get('Recaptcha.SiteKey'); AppConsts.subscriptionExpireNootifyDayCount = parseInt( abp.setting.get('App.TenantManagement.SubscriptionExpireNotifyDayCount') ); callback(); } ); } private static configureLuxon() { let luxonLocale = new LocaleMappingService().map('luxon', abp.localization.currentLanguage.name); DateTime.local().setLocale(luxonLocale); DateTime.utc().setLocale(luxonLocale); Settings.defaultLocale = luxonLocale; if (abp.clock.provider.supportsMultipleTimezone) { Settings.defaultZone = abp.timing.timeZoneInfo.iana.timeZoneId; } Date.prototype.toISOString = function () { return DateTime.fromJSDate(this) .setLocale('en') .setZone(abp.timing.timeZoneInfo.iana.timeZoneId) .toString(); }; Date.prototype.toString = function () { return DateTime.fromJSDate(this) .setLocale('en') .setZone(abp.timing.timeZoneInfo.iana.timeZoneId) .toString(); }; DateTime.prototype.toString = function () { let date = this.setLocale('en').setZone(abp.timing.timeZoneInfo.iana.timeZoneId) as DateTime; return date.toISO(); }; } }
DONE
That's it! Now we can make use of ASPNETZERO's localization capabilities in our minimalist ASPNETZERO Angular client!
-
0
SignalR
This documentation shows how to make use of ASPNETZERO's SignalR capabilities (public/non-authorized endpoint only).
Add package:
npm install --save --legacy-peer-deps @microsoft/signalr
Add assets and scripts to
./angular.json
:"assets": [ "src/favicon.ico", "src/assets", { "glob": "abp.signalr-client.js", "input": "node_modules/abp-web-resources/Abp/Framework/scripts/libs", "output": "/assets/abp" } ], "scripts": [ "src/assets/abp-web-resources/abp.js", "node_modules/@microsoft/signalr/dist/browser/signalr.min.js" ]
Adapt
./src/typings.d.ts
as follows:///<reference path="../node_modules/abp-web-resources/Abp/Framework/scripts/abp.d.ts"/> ///<reference path="../node_modules/abp-web-resources/Abp/Framework/scripts/libs/abp.signalr.d.ts"/>
Copy
./src/shared/helpers/signalr-helper.ts
from original ASPNETZERO Angular client (original name isSignalRHelper.ts
).
Adapt the file as follows:import { AppConsts } from '@shared/app-consts'; export class SignalRHelper { static initSignalR(callback: () => void): void { abp.signalr = { autoConnect: false, connect: undefined, hubs: undefined, qs: undefined, remoteServiceBaseUrl: AppConsts.remoteServiceBaseUrl, startConnection: undefined, url: '/signalr-public', }; let script = document.createElement('script'); script.src = `${AppConsts.appBaseUrl}/assets/abp/abp.signalr-client.js`; script.onload = () => { callback(); }; document.head.appendChild(script); } }
DONE
That's it! Now we can make use of ASPNETZERO's SignalR capabilities in our minimalist ASPNETZERO Angular client!
-
0
That's it for now ; )
-
0
Hi @alexanderpilhar
Thank you very much for sharing this :), it is really appriciated. If you have time in the future and if you can create a single markdown file with the content above, we can also publish it on https://blog.aspnetzero.com/