diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md
index d627f466ba..c8b73b08f3 100644
--- a/docs/en/UI/Angular/Localization.md
+++ b/docs/en/UI/Angular/Localization.md
@@ -130,6 +130,68 @@ this.store.selectSnapshot(
Localization resources are stored in the `localization` property of `ConfigState`.
+## RTL Support
+
+As of v2.9 ABP has RTL support. If you are generating a new project with v2.9 and above, everything is set, you do not need to do any changes. If you are migrating your project from an earlier version, please follow the 2 steps below:
+
+#### Step 1. Create Chunks for Bootstrap LTR and RTL
+
+Find [styles configuration in angular.json](https://angular.io/guide/workspace-config#style-script-config) and make sure the chunks in your project has `bootstrap-rtl.min` and `bootstrap-ltr.min` as shown below.
+
+```json
+{
+ "projects": {
+ "MyProjectName": {
+ "architect": {
+ "build": {
+ "options": {
+ "styles": [
+ {
+ "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css",
+ "inject": false,
+ "bundleName": "bootstrap-rtl.min"
+ },
+ {
+ "input": "node_modules/bootstrap/dist/css/bootstrap.min.css",
+ "inject": true,
+ "bundleName": "bootstrap-ltr.min"
+ },
+ {
+ "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
+ "inject": true,
+ "bundleName": "fontawesome-all.min"
+ },
+ {
+ "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css",
+ "inject": true,
+ "bundleName": "fontawesome-v4-shims.min"
+ },
+ "apps/dev-app/src/styles.scss"
+ ],
+ }
+ }
+ }
+ }
+ }
+}
+
+#### Step 2. Clear Lazy Loaded Fontawesome in AppComponent
+
+If you have created and injected chunks for Fontawesome as seen above, you no longer need the lazy loading in the `AppComponent` which was implemented before v2.9. Simply remove them. The `AppComponent` in the template of the new version looks like this:
+
+```js
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ template: `
+
+
+ `,
+})
+export class AppComponent {}
+```
+
## See Also
diff --git a/npm/ng-packs/angular.json b/npm/ng-packs/angular.json
index 39a10f827b..2b876fe21c 100644
--- a/npm/ng-packs/angular.json
+++ b/npm/ng-packs/angular.json
@@ -440,18 +440,27 @@
"assets": ["apps/dev-app/src/favicon.ico", "apps/dev-app/src/assets"],
"extractCss": true,
"styles": [
- "apps/dev-app/src/styles.scss",
- "node_modules/bootstrap/dist/css/bootstrap.min.css",
+ {
+ "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css",
+ "inject": false,
+ "bundleName": "bootstrap-rtl.min"
+ },
+ {
+ "input": "node_modules/bootstrap/dist/css/bootstrap.min.css",
+ "inject": true,
+ "bundleName": "bootstrap-ltr.min"
+ },
{
"input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
- "lazy": true,
+ "inject": true,
"bundleName": "fontawesome-all.min"
},
{
"input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css",
- "lazy": true,
+ "inject": true,
"bundleName": "fontawesome-v4-shims.min"
- }
+ },
+ "apps/dev-app/src/styles.scss"
],
"scripts": []
},
diff --git a/npm/ng-packs/apps/dev-app/src/app/app.component.ts b/npm/ng-packs/apps/dev-app/src/app/app.component.ts
index 8187365734..bf2a27962a 100644
--- a/npm/ng-packs/apps/dev-app/src/app/app.component.ts
+++ b/npm/ng-packs/apps/dev-app/src/app/app.component.ts
@@ -1,6 +1,4 @@
-import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
-import { Component, OnInit } from '@angular/core';
-import { forkJoin } from 'rxjs';
+import { Component } from '@angular/core';
@Component({
selector: 'app-root',
@@ -9,17 +7,4 @@ import { forkJoin } from 'rxjs';
`,
})
-export class AppComponent implements OnInit {
- constructor(private lazyLoadService: LazyLoadService) {}
-
- ngOnInit() {
- forkJoin(
- this.lazyLoadService.load(
- LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-v4-shims.min.css'),
- ),
- this.lazyLoadService.load(
- LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-all.min.css'),
- ),
- ).subscribe();
- }
-}
+export class AppComponent {}
diff --git a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts
index cec9d4a6cf..4ba89949fd 100644
--- a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts
+++ b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts
@@ -8,7 +8,7 @@ import { uuid } from '../utils';
providedIn: 'root',
})
export class LazyLoadService {
- readonly loaded = new Set();
+ readonly loaded = new Map();
loadedLibraries: { [url: string]: ReplaySubject } = {};
@@ -45,7 +45,7 @@ export class LazyLoadService {
throwError(new CustomEvent('error')),
),
),
- tap(() => this.loaded.add(strategy.path)),
+ tap(() => this.loaded.set(strategy.path, strategy.element)),
delay(100),
shareReplay({ bufferSize: 1, refCount: true }),
);
@@ -113,4 +113,14 @@ export class LazyLoadService {
});
});
}
+
+ remove(path: string): boolean {
+ const element = this.loaded.get(path);
+
+ if (!element) return false;
+
+ element.parentNode.removeChild(element);
+ this.loaded.delete(path);
+ return true;
+ }
}
diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts
index be89a751ed..f1f3abe30c 100644
--- a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts
+++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts
@@ -5,6 +5,8 @@ import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from './cross-origin.strat
import { DomStrategy, DOM_STRATEGY } from './dom.strategy';
export abstract class LoadingStrategy {
+ element: T;
+
constructor(
public path: string,
protected domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(),
@@ -14,10 +16,10 @@ export abstract class LoadingStrategy(): Observable {
+ this.element = this.createElement();
+
return of(null).pipe(
- switchMap(() =>
- fromLazyLoad(this.createElement(), this.domStrategy, this.crossOriginStrategy),
- ),
+ switchMap(() => fromLazyLoad(this.element, this.domStrategy, this.crossOriginStrategy)),
);
}
}
diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts
index 3596da2591..4868fb78f3 100644
--- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts
+++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts
@@ -49,7 +49,7 @@ describe('LazyLoadService', () => {
it('should emit a custom load event if loaded if resource is loaded before', done => {
const loadEvent = new CustomEvent('load');
- service.loaded.add(strategy.path);
+ service.loaded.set(strategy.path, null);
service.load(strategy).subscribe(event => {
expect(event).toEqual(loadEvent);
@@ -57,6 +57,32 @@ describe('LazyLoadService', () => {
});
});
});
+
+ describe('#remove', () => {
+ const service = new LazyLoadService();
+
+ it('should remove an already lazy loaded element and return true', () => {
+ const script = document.createElement('script');
+ document.body.appendChild(script);
+ service.loaded.set('x', script);
+
+ expect(document.body.lastElementChild).toBe(script);
+
+ const result = service.remove('x');
+
+ expect(document.body.lastElementChild).toBeNull();
+ expect(service.loaded.has('x')).toBe(false);
+ expect(result).toBe(true);
+ });
+
+ it('should return false when path not found', () => {
+ service.loaded.set('foo', null);
+
+ const result = service.remove('bar');
+
+ expect(result).toBe(false);
+ });
+ });
});
describe('LazyLoadService (Deprecated)', () => {
diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts
index 4b950cf533..4215e6eee5 100644
--- a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts
+++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts
@@ -39,6 +39,7 @@ describe('ScriptLoadingStrategy', () => {
const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy);
strategy.createStream().subscribe(event => {
+ expect(strategy.element.tagName).toBe('SCRIPT');
expect(event.detail.crossOrigin).toBe('use-credentials');
done();
});
@@ -78,6 +79,7 @@ describe('StyleLoadingStrategy', () => {
const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy);
strategy.createStream().subscribe(event => {
+ expect(strategy.element.tagName).toBe('LINK');
expect(event.detail.crossOrigin).toBe('use-credentials');
done();
});
diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts
index f5319edc2d..1409c0da21 100644
--- a/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts
+++ b/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts
@@ -2,9 +2,53 @@ import {
createLocalizationPipeKeyGenerator,
createLocalizer,
createLocalizerWithFallback,
+ getLocaleDirection,
} from '../utils/localization-utils';
describe('Localization Utils', () => {
+ describe('#getLocaleDirection', () => {
+ test.each`
+ locale | expected
+ ${undefined} | ${'ltr'}
+ ${null} | ${'ltr'}
+ ${''} | ${'ltr'}
+ ${'en'} | ${'ltr'}
+ ${'en-US'} | ${'ltr'}
+ ${'pt'} | ${'ltr'}
+ ${'pt-PT'} | ${'ltr'}
+ ${'ar'} | ${'rtl'}
+ ${'ar-AE'} | ${'rtl'}
+ ${'ar-QA'} | ${'rtl'}
+ ${'ckb'} | ${'rtl'}
+ ${'ckb-IR'} | ${'rtl'}
+ ${'fa'} | ${'rtl'}
+ ${'fa-AF'} | ${'rtl'}
+ ${'he'} | ${'rtl'}
+ ${'ks'} | ${'rtl'}
+ ${'ksb'} | ${'ltr'}
+ ${'ksf'} | ${'ltr'}
+ ${'ksh'} | ${'ltr'}
+ ${'lrc'} | ${'rtl'}
+ ${'lrc-IQ'} | ${'rtl'}
+ ${'mzn'} | ${'rtl'}
+ ${'pa'} | ${'ltr'}
+ ${'pa-Arab'} | ${'rtl'}
+ ${'ps'} | ${'rtl'}
+ ${'ps-PK'} | ${'rtl'}
+ ${'sd'} | ${'rtl'}
+ ${'ug'} | ${'rtl'}
+ ${'ur'} | ${'rtl'}
+ ${'ur-IN'} | ${'rtl'}
+ ${'uz'} | ${'ltr'}
+ ${'uz-Arab'} | ${'rtl'}
+ ${'yi'} | ${'rtl'}
+ ${'zh'} | ${'ltr'}
+ ${'zh-Hans'} | ${'ltr'}
+ `('should return $expected when $locale is given as parameter', ({ locale, expected }) => {
+ expect(getLocaleDirection(locale)).toBe(expected);
+ });
+ });
+
describe('#createLocalizer', () => {
const localize = createLocalizer({
values: { foo: { bar: 'baz' }, x: { y: 'z' } },
diff --git a/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts
index ed094f7fc7..09478c8ed8 100644
--- a/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts
+++ b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts
@@ -1,5 +1,14 @@
import { ApplicationConfiguration } from '../models/application-configuration';
+// This will not be necessary when only Angukar 9.1+ is supported
+export function getLocaleDirection(locale: string): 'ltr' | 'rtl' {
+ return /^(ar(-[A-Z]{2})?|ckb(-IR)?|fa(-AF)?|he|ks|lrc(-IQ)?|mzn|pa-Arab|ps(-PK)?|sd|ug|ur(-IN)?|uz-Arab|yi)$/.test(
+ locale,
+ )
+ ? 'rtl'
+ : 'ltr';
+}
+
export function createLocalizer(localization: ApplicationConfiguration.Localization) {
return (resourceName: string, key: string, defaultValue: string) => {
if (resourceName === '_') return key;
diff --git a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts
index 0d9ebcc300..2de9931873 100644
--- a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts
+++ b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts
@@ -2,7 +2,6 @@ export default `
.content-header-title {
font-size: 24px;
}
-
.entry-row {
margin-bottom: 15px;
}
@@ -26,17 +25,25 @@ export default `
border-radius: 0.25rem;
min-height: 44px;
}
+[dir=rtl] .navbar .dropdown-menu a {
+ text-align: right!important;
+}
.navbar .dropdown-submenu a::after {
transform: rotate(-90deg);
position: absolute;
right: 16px;
top: 18px;
}
+[dir=rtl] .navbar .dropdown-submenu a::after {
+ transform: rotate(90deg);
+ left: 16px;
+ right: auto;
+ top: 20px;
+}
.navbar .dropdown-submenu .dropdown-menu {
top: 0;
left: 100%;
}
-
.card-header .btn {
padding: 2px 6px;
}
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html
index eeaf0d40d9..6cbea56304 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html
+++ b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html
@@ -21,7 +21,7 @@