Skip to content

Commit

Permalink
feat(config): adding AppConfig service
Browse files Browse the repository at this point in the history
to load remote overlay config and  featureFlags
  • Loading branch information
xmlking committed Dec 30, 2019
1 parent 7daffcd commit dd21bf8
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Add files here to ignore them from prettier formatting
*.js
*.js.map
*.d.ts
*.md
*.min.*

/dist
/coverage
1 change: 1 addition & 0 deletions PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ ng g directive directives/mask/mask --selector=ngxMask --project=ngx-utils --mo
ng g lib blog --routing --lazy --parent-module=libs/home/src/lib/home.module.ts --tags=private-module --defaults -d
ng g component containers/blogList --project=blog -d
ng g component containers/blog --project=blog -d
ng g service services/highlight --project=blog --skip-tests -d

# generate components for `toolbar` Module
ng g lib toolbar --tags=private-module --defaults -d
Expand Down
3 changes: 3 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# TODO

1. [feature flags](https://www.bennadel.com/blog/3709-loading-and-using-remote-feature-flags-in-angular-9-0-0-next-12.htm)
8 changes: 8 additions & 0 deletions apps/webapp/src/assets/data/ui-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"DOCS_BASE_URL": "http://sumo.com/docs",
"featureFlags": {
"feature-a": true,
"feature-b": false,
"feature-c": true
}
}
13 changes: 13 additions & 0 deletions apps/webapp/src/environments/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as packageJson from '../../../../package.json';

const base = document.querySelector('base');

export default {
appName: 'YETI',
secret: 'SECRET',
apiToken: 'SECRET_TOKEN',
baseUrl: (base && base.href) || window.location.origin + '/',
versions: {
app: packageJson.version
}
};
11 changes: 10 additions & 1 deletion apps/webapp/src/environments/environment.mock.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
export const environment = {
import { IEnvironment } from '@env/ienvironment';
import sharedEnvironment from './base';

export const environment: IEnvironment = {
...sharedEnvironment,
production: true,
envName: 'mock',

REMOTE_CONFIG_URL: '/yeti/assets/data/ui-config.json',
API_BASE_URL: 'https://api.kashmora.com/api',

plugins: [
// HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
// passThruUnknownUrl: true,
// delay: 1000
// })
],

auth: {
clientId:
'791772336084-vkt37abstm1du92ofdmhgi30vgd7t0oa.apps.googleusercontent.com',
Expand Down
11 changes: 10 additions & 1 deletion apps/webapp/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
export const environment = {
import { IEnvironment } from '@env/ienvironment';
import sharedEnvironment from './base';

export const environment: IEnvironment = {
...sharedEnvironment,
production: true,
envName: 'prod',

REMOTE_CONFIG_URL: '/assets/data/ui-config.json',
API_BASE_URL: 'https://api.kashmora.com/api',

plugins: [],

auth: {
clientId:
'791772336084-vkt37abstm1du92ofdmhgi30vgd7t0oa.apps.googleusercontent.com',
Expand Down
10 changes: 9 additions & 1 deletion apps/webapp/src/environments/environment.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
import { IEnvironment } from '@env/ienvironment';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
import sharedEnvironment from './base';

export const environment = {
export const environment: IEnvironment = {
...sharedEnvironment,
production: false,
envName: 'dev',

REMOTE_CONFIG_URL: '/assets/data/ui-config.json',
API_BASE_URL: 'http://localhost:3000/api',

plugins: [
NgxsReduxDevtoolsPluginModule.forRoot({ maxAge: 10 }),
NgxsLoggerPluginModule.forRoot()
],

auth: {
clientId:
'791772336084-vkt37abstm1du92ofdmhgi30vgd7t0oa.apps.googleusercontent.com',
Expand Down
20 changes: 20 additions & 0 deletions apps/webapp/src/environments/ienvironment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// import { OidcProviderConfig } from '@ngx-starter-kit/oidc';
import { ModuleWithProviders } from '@angular/core';

export type LogLevel = 'debug' | 'info' | 'warn' | 'error';

export interface IEnvironment {
production: boolean;
envName: string;
REMOTE_CONFIG_URL: string;

// Enables use of ng.profiler.timeChangeDetection(); in browser console
enableDebugTools?: boolean;
logLevel?: LogLevel;

[key: string]: any;
plugins: ModuleWithProviders<any>[];
featureFlags?: {
[key: string]: boolean;
};
}
18 changes: 18 additions & 0 deletions libs/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

This library was generated with [Nx](https://nx.dev).

### Service

1. AppConfigService - loading remote config and featureFlags
2. PageTitleService - set page title from breadcrumbs
3. AnalyticsService - Google Analytics

### Stores

1. AuthState - keep track of auth state

### Handlers

1. RouteHandler - update page title and send page views to google analytics

### Interceptors

1. ErrorInterceptor - report http errors

This module should contain minimal shared `Guards`, `Services` , `State` Injectables

It should not contain `Components`, `Directives` and `Pipes`. For that, use `SharedModule`
Expand Down
2 changes: 2 additions & 0 deletions libs/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './lib/core.module';
export { AnalyticsService } from './lib/services/analytics.service';
export { LayoutService } from './lib/services/layout.service';
export { AppConfigService } from './lib/services/app-config.service';
export { PageTitleService } from './lib/services/page-title.service';
28 changes: 25 additions & 3 deletions libs/core/src/lib/core.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, NgModule, Optional, SkipSelf } from '@angular/core';
import { environment } from '@env/environment';
import {
Expand All @@ -23,10 +24,20 @@ import {
} from '@ngxs/router-plugin';
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
import { NgxsModule } from '@ngxs/store';
import { AuthHandler } from './handler/auth.handler';
import { CustomRouterStateSerializer } from './handler/custom-router-state.serializer';
import { RouteHandler } from './handler/route.handler';
import { AuthHandler } from './handlers/auth.handler';
import { CustomRouterStateSerializer } from './handlers/custom-router-state.serializer';
import { RouteHandler } from './handlers/route.handler';
import { ErrorInterceptor } from './interceptors/error.interceptor';
import { AppConfigService } from './services/app-config.service';
import { AuthState } from './state/auth.state';

// appConfig initializer factory function
const appConfigInitializerFn = (appConfig: AppConfigService) => {
return () => {
return appConfig.load();
};
};

// Noop handler for factory function
export function noop() {
return () => {};
Expand Down Expand Up @@ -79,6 +90,17 @@ export function noop() {
provide: RouterStateSerializer,
useClass: CustomRouterStateSerializer
},
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true
},
{
provide: APP_INITIALIZER,
useFactory: appConfigInitializerFn,
deps: [AppConfigService],
multi: true
},
{
provide: APP_INITIALIZER,
useFactory: noop,
Expand Down
File renamed without changes.
File renamed without changes.
45 changes: 45 additions & 0 deletions libs/core/src/lib/interceptors/error.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
// import { Store } from '@ngxs/store';
// import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';

@Injectable({
providedIn: 'root'
})
export class ErrorInterceptor implements HttpInterceptor {

constructor(/*private snackBar: MatSnackBar, private store : Store*/) {}

intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(catchError(this.handleError));
}

/* tslint:disable */
public handleError = (errorRes: HttpErrorResponse) => {
const {
error: { status, error, message }
} = errorRes;
// Do messaging and error handling here
// this.snackBar.open(
// `Error ! ${message}`,
// '',
// ErrorInterceptor.snackBarConfig
// );
console.error(
`Backend Error ! status: ${status}, error: ${error}, message: ${message}`
);

return throwError(errorRes);
};
}
32 changes: 32 additions & 0 deletions libs/core/src/lib/services/app-config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '@env/environment';
import { IEnvironment } from '@env/ienvironment';

/**
* In Components, inject `AppConfigService` and usage:
* this.isShowingFeatureA = appConfig.config.featureFlags[ 'feature-a' ];
*/
@Injectable({
providedIn: 'root'
})
export class AppConfigService {
private configUrl = environment.REMOTE_CONFIG_URL;
private configPrivate = environment;

constructor(private http: HttpClient) {}

async load(configUrl = this.configUrl) {
try {
const remoteConfig = await this.http
.get<IEnvironment>(configUrl)
.toPromise();
this.configPrivate = { ...environment, ...remoteConfig };
} catch {
console.error(`Unable to load remote config url ${configUrl}`);
}
}
get config() {
return this.configPrivate;
}
}

0 comments on commit dd21bf8

Please sign in to comment.