diff --git a/npm/ng-packs/packages/feature-management/README.md b/npm/ng-packs/packages/feature-management/README.md
new file mode 100644
index 0000000000..d040ec455a
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/README.md
@@ -0,0 +1 @@
+
@abp/ng.feature-management
diff --git a/npm/ng-packs/packages/feature-management/karma.conf.js b/npm/ng-packs/packages/feature-management/karma.conf.js
new file mode 100644
index 0000000000..65f905a52e
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/karma.conf.js
@@ -0,0 +1,32 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../../coverage/feature-management'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ restartOnFileChange: true
+ });
+};
diff --git a/npm/ng-packs/packages/feature-management/ng-package.json b/npm/ng-packs/packages/feature-management/ng-package.json
new file mode 100644
index 0000000000..0293d70b2e
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/ng-package.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/feature-management",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ },
+ "deleteDestPath": false
+}
diff --git a/npm/ng-packs/packages/feature-management/package.json b/npm/ng-packs/packages/feature-management/package.json
new file mode 100644
index 0000000000..f41fd28fa1
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "@abp/ng.feature-management",
+ "version": "0.0.1"
+}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/actions/feature-management.actions.ts b/npm/ng-packs/packages/feature-management/src/lib/actions/feature-management.actions.ts
new file mode 100644
index 0000000000..dee5da664a
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/actions/feature-management.actions.ts
@@ -0,0 +1,11 @@
+import { FeatureManagement } from '../models';
+
+export class GetFeatures {
+ static readonly type = '[FeatureManagement] Get Features';
+ constructor(public payload: FeatureManagement.Provider) {}
+}
+
+export class UpdateFeatures {
+ static readonly type = '[FeatureManagement] Update Features';
+ constructor(public payload: FeatureManagement.Provider & FeatureManagement.Features) {}
+}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/actions/index.ts b/npm/ng-packs/packages/feature-management/src/lib/actions/index.ts
new file mode 100644
index 0000000000..66678fb322
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/actions/index.ts
@@ -0,0 +1 @@
+export * from './feature-management.actions';
diff --git a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html
new file mode 100644
index 0000000000..27cb5728f5
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html
@@ -0,0 +1,32 @@
+
+
+ {{ 'AbpTenantManagement::Permission:ManageFeatures' | abpLocalization }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
new file mode 100644
index 0000000000..b2ab6e71cf
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
@@ -0,0 +1,106 @@
+import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
+import { Select, Store } from '@ngxs/store';
+import { Observable } from 'rxjs';
+import { GetFeatures, UpdateFeatures } from '../../actions';
+import { FeatureManagement } from '../../models/feature-management';
+import { FeatureManagementState } from '../../states';
+import { FormGroup, FormControl } from '@angular/forms';
+import { pluck, tap } from 'rxjs/operators';
+
+@Component({
+ selector: 'abp-feature-management',
+ templateUrl: './feature-management.component.html',
+})
+export class FeatureManagementComponent implements OnChanges {
+ @Input()
+ providerKey: string;
+
+ @Input()
+ providerName: string;
+
+ protected _visible;
+
+ @Input()
+ get visible(): boolean {
+ return this._visible;
+ }
+
+ set visible(value: boolean) {
+ this._visible = value;
+ this.visibleChange.emit(value);
+ }
+
+ @Output()
+ visibleChange = new EventEmitter();
+
+ @Select(FeatureManagementState.getFeatures)
+ features$: Observable;
+
+ modalBusy: boolean = false;
+
+ form: FormGroup;
+
+ constructor(private store: Store) {}
+
+ ngOnChanges({ visible }: SimpleChanges): void {
+ if (!visible) return;
+
+ if (visible.currentValue) {
+ this.openModal();
+ } else if (this.visible && visible.currentValue === false) {
+ this.visible = false;
+ }
+ }
+
+ openModal() {
+ if (!this.providerKey || !this.providerName) {
+ throw new Error('Provider Key and Provider Name are required.');
+ }
+
+ this.getFeatures();
+ }
+
+ getFeatures() {
+ this.store
+ .dispatch(new GetFeatures({ providerKey: this.providerKey, providerName: this.providerName }))
+ .pipe(pluck('FeatureManagementState', 'features'))
+ .subscribe(features => {
+ this.buildForm(features);
+ this.visible = true;
+ });
+ }
+
+ buildForm(features) {
+ const formGroupObj = {};
+
+ for (let i = 0; i < features.length; i++) {
+ formGroupObj[i] = new FormControl(features[i].value === 'false' ? null : features[i].value);
+ }
+
+ this.form = new FormGroup(formGroupObj);
+ }
+
+ save() {
+ this.modalBusy = true;
+
+ let features = this.store.selectSnapshot(FeatureManagementState.getFeatures);
+
+ features = features.map((feature, i) => ({
+ name: feature.name,
+ value: !this.form.value[i] || this.form.value[i] === 'false' ? null : this.form.value[i],
+ }));
+
+ this.store
+ .dispatch(
+ new UpdateFeatures({
+ providerKey: this.providerKey,
+ providerName: this.providerName,
+ features,
+ }),
+ )
+ .subscribe(() => {
+ this.modalBusy = false;
+ this.visible = false;
+ });
+ }
+}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/components/index.ts b/npm/ng-packs/packages/feature-management/src/lib/components/index.ts
new file mode 100644
index 0000000000..64f8bd0fc5
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/components/index.ts
@@ -0,0 +1 @@
+export * from './feature-management/feature-management.component';
diff --git a/npm/ng-packs/packages/feature-management/src/lib/feature-management.module.ts b/npm/ng-packs/packages/feature-management/src/lib/feature-management.module.ts
new file mode 100644
index 0000000000..ee3eca6d42
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/feature-management.module.ts
@@ -0,0 +1,13 @@
+import { CoreModule } from '@abp/ng.core';
+import { ThemeSharedModule } from '@abp/ng.theme.shared';
+import { NgModule } from '@angular/core';
+import { FeatureManagementComponent } from './components/feature-management/feature-management.component';
+import { NgxsModule } from '@ngxs/store';
+import { FeatureManagementState } from './states/feature-management.state';
+
+@NgModule({
+ declarations: [FeatureManagementComponent],
+ imports: [CoreModule, ThemeSharedModule, NgxsModule.forFeature([FeatureManagementState])],
+ exports: [FeatureManagementComponent],
+})
+export class FeatureManagementModule {}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/models/feature-management.ts b/npm/ng-packs/packages/feature-management/src/lib/models/feature-management.ts
new file mode 100644
index 0000000000..05a0a28c99
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/models/feature-management.ts
@@ -0,0 +1,29 @@
+export namespace FeatureManagement {
+ export interface State {
+ features: Feature[];
+ }
+
+ export interface ValueType {
+ name: string;
+ properties: object;
+ validator: object;
+ }
+
+ export interface Feature {
+ name: string;
+ value: string;
+ description?: string;
+ valueType?: ValueType;
+ depth?: number;
+ parentName?: string;
+ }
+
+ export interface Features {
+ features: Feature[];
+ }
+
+ export interface Provider {
+ providerName: string;
+ providerKey: string;
+ }
+}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/models/index.ts b/npm/ng-packs/packages/feature-management/src/lib/models/index.ts
new file mode 100644
index 0000000000..d6ed522ae0
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/models/index.ts
@@ -0,0 +1 @@
+export * from './feature-management';
diff --git a/npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts b/npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts
new file mode 100644
index 0000000000..5f6c0eed6e
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/services/feature-management.service.ts
@@ -0,0 +1,35 @@
+import { Injectable } from '@angular/core';
+import { RestService, Rest } from '@abp/ng.core';
+import { Store } from '@ngxs/store';
+import { Observable } from 'rxjs';
+import { FeatureManagement } from '../models';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class FeatureManagementService {
+ constructor(private rest: RestService, private store: Store) {}
+
+ getFeatures(params: FeatureManagement.Provider): Observable {
+ const request: Rest.Request = {
+ method: 'GET',
+ url: '/api/abp/features',
+ params,
+ };
+ return this.rest.request(request);
+ }
+
+ updateFeatures({
+ features,
+ providerKey,
+ providerName,
+ }: FeatureManagement.Provider & FeatureManagement.Features): Observable {
+ const request: Rest.Request = {
+ method: 'PUT',
+ url: '/api/abp/features',
+ body: { features },
+ params: { providerKey, providerName },
+ };
+ return this.rest.request(request);
+ }
+}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/services/index.ts b/npm/ng-packs/packages/feature-management/src/lib/services/index.ts
new file mode 100644
index 0000000000..10fceceafe
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/services/index.ts
@@ -0,0 +1 @@
+export * from './feature-management.service';
diff --git a/npm/ng-packs/packages/feature-management/src/lib/states/feature-management.state.ts b/npm/ng-packs/packages/feature-management/src/lib/states/feature-management.state.ts
new file mode 100644
index 0000000000..a02e075bdb
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/states/feature-management.state.ts
@@ -0,0 +1,34 @@
+import { Action, Selector, State, StateContext } from '@ngxs/store';
+import { tap } from 'rxjs/operators';
+import { GetFeatures, UpdateFeatures } from '../actions/feature-management.actions';
+import { FeatureManagement } from '../models/feature-management';
+import { FeatureManagementService } from '../services/feature-management.service';
+
+@State({
+ name: 'FeatureManagementState',
+ defaults: { features: {} } as FeatureManagement.State,
+})
+export class FeatureManagementState {
+ @Selector()
+ static getFeatures({ features }: FeatureManagement.State) {
+ return features;
+ }
+
+ constructor(private featureManagementService: FeatureManagementService) {}
+
+ @Action(GetFeatures)
+ getFeatures({ patchState }: StateContext, { payload }: GetFeatures) {
+ return this.featureManagementService.getFeatures(payload).pipe(
+ tap(({ features }) =>
+ patchState({
+ features,
+ }),
+ ),
+ );
+ }
+
+ @Action(UpdateFeatures)
+ updateFeatures(_, { payload }: UpdateFeatures) {
+ return this.featureManagementService.updateFeatures(payload);
+ }
+}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/states/index.ts b/npm/ng-packs/packages/feature-management/src/lib/states/index.ts
new file mode 100644
index 0000000000..5c32d87889
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/lib/states/index.ts
@@ -0,0 +1 @@
+export * from "./feature-management.state";
diff --git a/npm/ng-packs/packages/feature-management/src/public-api.ts b/npm/ng-packs/packages/feature-management/src/public-api.ts
new file mode 100644
index 0000000000..047ac00914
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/public-api.ts
@@ -0,0 +1,2 @@
+export * from './lib/feature-management.module';
+export * from './lib/components';
diff --git a/npm/ng-packs/packages/feature-management/src/test.ts b/npm/ng-packs/packages/feature-management/src/test.ts
new file mode 100644
index 0000000000..978c64fb83
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/src/test.ts
@@ -0,0 +1,21 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone';
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/npm/ng-packs/packages/feature-management/tsconfig.lib.json b/npm/ng-packs/packages/feature-management/tsconfig.lib.json
new file mode 100644
index 0000000000..bd23948e59
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/tsconfig.lib.json
@@ -0,0 +1,26 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/lib",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": [
+ "dom",
+ "es2018"
+ ]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true
+ },
+ "exclude": [
+ "src/test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/npm/ng-packs/packages/feature-management/tsconfig.spec.json b/npm/ng-packs/packages/feature-management/tsconfig.spec.json
new file mode 100644
index 0000000000..16da33db07
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/tsconfig.spec.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/test.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/npm/ng-packs/packages/feature-management/tslint.json b/npm/ng-packs/packages/feature-management/tslint.json
new file mode 100644
index 0000000000..8c3919ea62
--- /dev/null
+++ b/npm/ng-packs/packages/feature-management/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "abp",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "abp",
+ "kebab-case"
+ ]
+ }
+}