ngx-rbk-demo v1.4.5
1. Setup
Install and setup all dependencies needed by the library
Install the library
`npm install ngx-rbk-utils`Import the
NgxRbkUtilsModulein yourAppModuleand pass thebuildState()function in theNgxsModule.forRoot()method. Also import theToastModulefromprimeng. Also add a provider forNgxRbkUtilsConfigIMPORTANT: you must import the module BEFORE all other
ngxsmodulesExample:
@NgModule({ declarations: [ ... ], imports: [ ... NgxRbkUtilsModule.forRoot(rbkConfig), // before NGXS modules ... NgxsModule.forRoot(buildState(), { developmentMode: !environment.production }), // call the buildState() NgxsRouterPluginModule.forRoot(), NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production}), ... ], providers: [{ provide: NgxRbkUtilsConfig, useValue: rbkConfig }], bootstrap: [] }) export class AppModule { ... }In the constructor of the
AppModule, inject the NGRXStoreand dispatch the following action:export class AppModule { constructor(private store: Store) { this.store.dispatch(new ApplicationActions.NgRxInitialized()); } }Stil in
AppModule, define the following as provider:providers: [{ provide: NgxRbkUtilsConfig, useValue: rbkConfig }Obs: This is required for injection of NgxRbkUtilsConfig into auth.guard
In your
AppComponentinject theBoilerplateServiceand call the.init()method.constructor(private boilerplateService: BoilerplateService) { this.boilerplateService.init(); }In the
.forRoot()method of the library you need to pass a configuration object. Please see the configuration section for the details.
Define your
AppStateModelfor intellisense purposesimport { AppStateModel as RootStateModel } from 'ngx-rbk-utils'; export interface AppStateModel extends RootStateModel { database: { state1: State1StateModel, state2: State2DbStateModel, state3: State3DbStateModel, }, features: { feature1: Feature1StateModel, feature2: Feature2StateModel, }; }Add
<p-toast>and<smz-general-dialog>to yourapp.component.htmlMake sure you don't import
MessageServicefromprimengin any of modules/components or theToastActionsfrom the library will not workIn the constructor of your error page, clear the local storage with (or at least clear the properties that store the accessToken and the refreshTokemn):
constructor() {
localStorage.clear();
}2. Configuration file
import { NgxRbkUtilsConfig } from 'ngx-rbk-utils';
export const rbkConfig: NgxRbkUtilsConfig = {
// Application name, will be shown in the browser's tab name
applicationName: string,
useTitleService?: boolean, // Flag to indicate wheter the TitleService should be initialized or not.
routes: {
// Url path of the landing page of your application, this will be used to redirect the user when he tries to access a protected route and is not authenticated. Ex.: '/'
nonAuthenticatedRoute: string,
// Url path of the authenticated root page of your application, this will be used to redirect the user when he tries to access a protected route, is authenticated but does not have access to it. Ex.: '/editor'
authenticatedRoute: string,
// Url path of the login page of your application, this will be used to redirect the user if the library fails to refresh the JWT access token. Ex.: '/login'
login: string,
// Url path of the error page of your application this will be used to redirect the user if a request fail with the following status:
// 500, 0 or unmapped status. Ex.: '/error
error: string
},
authentication: {
login: {
// Full url of your login endpoint, must be POST and accepts the following object: { username: string, password: string }
url: string,
// How you want to handle the errors returned by this endpoint, possible values are 'toast', 'dialog' nad 'none'
errorHandlingType: string,
// Name of the access token property in the endpoint response
responsePropertyName: string,
// How you want to handle the loading state on the store, possible values are 'global', 'local' and 'none'
loadingBehavior: string,
},
refreshToken: {
// Full url of your login endpoint, must be POST and accepts the following object: { username: string, password: string }
url: `https://dev.meuencartedigital.com.br/auth/refresh-token`,
// How you want to handle the errors returned by this endpoint, possible values are 'toast', 'dialog' nad 'none'
loadingBehavior: 'none',
// Name of the access token property in the endpoint response
errorHandlingType: 'toast',
// How you want to handle the loading state on the store, possible values are 'global', 'local' and 'none'
responsePropertyName: 'refreshToken',
// Optional extra properties you can pass to refresh token api for validation. ex: {username: 'name', applicationId:'appId'}
extraProperties: {[name: string]: string}
},
// Array of configuration objects for claims that need to be read from the access token. Please see the authorization section for more details
accessTokenClaims: {claimName: string, propertyName: string, type: string} []
},
state: {
// Setup for the database part of the state
database: {[name: string]: DatabaseStateParameters},
// Setup for the feature part of the state
feature: {[name: string]: DatabaseStateParameters}
},
httpBehaviors: {
// Default parameters to be used in all HTTP requests on services that inherit from BaseApiService
defaultParameters: {
compression: false,
authentication: true,
needToRefreshToken: true,
loadingBehavior: 'global',
errorHandlingType: 'toast',
localLoadingTag: null,
restoreStateOnError: true
}
},
// Default contiguration for the PrimeNG Toast
toastConfig: {
severity: 'success',
life: 3000,
sticky: false,
closable: true,
successTitle: 'SUCESSO',
errorTitle: 'ERRO',
warningTitle: 'AVISO',
infoTitle: 'INFORMAÇÃO',
},
// Default dialog titles
dialogsConfig: {
errorDialogTitle: 'ERRO',
warningDialogTitle: 'ALERTA'
}
};3. Features
Actions
The library uses many internal NGXS actions and exposes a series of them to the user. The following list constains the actions of the library, organized by namespace:
DatabaseActions
Clear: cleans theDatabaseStore, using the callbacks specified in the config file (this action is automatically called when theApplicationActions.Logoutaction is dispatched).Restore: re-emits the entire database store, in its current state, to force UI updates. This action can be manually dispatched but it's automatically dispatched everytime an http request returns a code different than 200. This behavior can be overrided for each individual request using therestoreStateOnErrorproperty in thehttpBehaviorssection of the config file.
FeatureActions
Clear: cleans theFeatureStore, using the callbacks specified in the config file. (this action is automatically called when theApplicationActions.Logoutaction is dispatched).Restore: re-emits the entire features store, in its current state, to force UI updates. This action can be manually dispatched but it's automatically dispatched everytime an http request returns a code different than 200. This behavior can be overrided for each individual request using therestoreStateOnErrorproperty in thehttpBehaviorssection of the config file.
GlobalActions
Clear: cleans theGlobalStore, using the default data (this action is automatically called when theApplicationActions.Logoutaction is dispatched).Restore: re-emits the entire global store, in its current state, to force UI updates. This action can be manually dispatched but it's automatically dispatched everytime an http request returns a code different than 200. This behavior can be overrided for each individual request using therestoreStateOnErrorproperty in thehttpBehaviorssection of the config file.
AuthenticationActions
The
RemoteLoginandLogoutactions should be manually fired by the client application.
RemoteLogin: this action should be fired by the client application when the user clicks a LOGIN button. It will fire the login http request using the parameters of the config file. If this succeeds theRemoteLoginSuccessactions is dispatched. If it fails the error is handled either by showing a dialog with the error or a toast as set in the config file. The config section of the login request in the config file allows the user to pass extra static parameters with the login http request.Logout: this action should be manually dispatched when the user sign out.
The following actions are automatically dispatched inside the library, in common use cases there is no need to dispatch or handle them:
LocalLogin: when this action is dispatched the library tries to authenticate using the access token in the browser local storage.LocalLoginSuccess: this action is dispatched when the library successfully authenticate the user using the data stored in the local storage. This action automatically starts the process of loading the database stores from your API.LocalLoginFailure: this action is dispatched when the library failed to authenticate using the data in the local storage.RemoteLoginSuccess: this actions is dispatched when the login http resquests returns200and it automatically dispatched aNavigateaction to theauthenticatedRootroute path in the config file.RefreshTokenSuccess: this action is dispatched everytime an access token is successfully refreshed.
ApplicationActions
IMPORTANT: these actions are not meant to be dispatched by the client application, they are for internal use only. But since they will show in the Redux Dev Tools, they will be described here.
HandleHttpErrorWithDialog: this action is dispatched when an error handling flag in the config file set todialog.HandleHttpErrorWithToast: this action is dispatched when an error handling flag in the config file set totoast.StartGlobalLoading: this action is dispatched when an http request is fired with the loading behavior flag set toglobalnad no other request is in progress.StopGlobalLoading: this action is dispatched when the last http request in progress is finished.NgRxInitialized: this actions is dispatched to flag whenNGXSis initialized in the client application.PushLocalLoading: this action is dispatched when an http request is started with the loading behavior flag set totoastPopLocalLoading: this action is dispatched when an http request with the loading behavior flag set totoastis finished
Selectors
The library automatically creates the global piece of the store containing application and authentication related data. To use this data the user manually query the store or use the selectors already exposed by the library:
AuthenticationSelectors
accessToken: returns the access token asstringrefreshToken: returns the refresh token asstringisAuthenticated: returns true if the user is authenticateduserdata: return the whole object constructed using the JWT token data. Since its properties are dynamic (set in the config file) this selector returns an object of typeanyand should be cast to your user model as can be seen in the example below.const data = this.store.selectSnapshot(AuthenticationSelectors.userdata) as MyUserModelusername: returns the name of user as it is in the JWT access token in the claim, but only if a propertyusernameis setup in theaccessTokenClaimsproperty in the config file.hasGroupOfClaimAccess: receives an array of claims and returns true if the user has all claims in its access token. The claims can have a domain or not. More details in theClaims DomainsectionhasClaimAccess: receives a claim and returns true if the user has this claim in its access token. The claim can have a domain or not. More details in theClaims Domainsection
ApplicationSelectors
globalIsLoading: returns true if there is any http request (using theglobalin the loading behavior flag) in progress.isDatabaseStateInitialized: returns true if all pieces of thedatabasestore are initialized.isWaitingRequest: receives a string containing a local loading http tag and returns true if there is any http request with this tag in progress.
Database state auto initialization
One of the main features of this library is to easy the burden of loading shared states (called as database states here) and knowing if their data is already loaded.
In the concept of database stores all data that should be readibly available to the application and shared between components should be loaded before component initialization.
This way all this loading is centralized in a single part of the code and you don't have to manually fire their load actions in multiple parts of the code.
This approach works very well and make the perfect balance between performance and resources. Only the states needed for a given component will be loaded and only if they are not ready.
One of the greatest features of NGXS is also the one that can gives more headaches in some scenarios, the selectSnapshot function. When the users enters your application using a route that needs certain data to render something, Angular will load the component before the data arrive from the backend and you're using selectSnapshot instead of select from NGXS.
The obvious solution is to use the select function. But in many cases the select is an overkill since it returns an Observable and it's a lot easier to work directly with the results of a variable. This is specially true for the contents of dropdown or lists. For this reason we prefer to use the selectSnapshot feature of NGXS, but this gets the value of the state in the moment it was called and it's never updated again. So we need to be sure to call it only we are sure that the data is loaded in the store.
To solve this problem the library exposes a route guard called RbkDatabaseGuard. You specify the states that are needed for a given route and it makes this data to be ready before letting Angular load the component.
The following code show how the setup is made in the route:
{
path: 'my-route',
canActivate: [ RbkAuthGuard, RbkDatabaseStateGuard ],
component: MyComponent,
data: { title: 'Title', breadcrumb: 'Title', requiredStates: ['accounts', 'categories'] },
}As can be seen, the RbkDatabaseStateGuard is being used in the canActivate property of the route and the required states for the route are specified in the array of the requiredStates property of the data property.
The values used in the requiredStates property must match the names of the states in the configuration file.
Rembember that if any of the required states needs authentication, the
RbkAuthGuardguard must be specified before theRbkDatabaseStateGuardguard.
There is also the DatabaseSelectors.areStatesInitialized selector that lets you manually check if some states are initialized or not. It receives an array of strings representing the states you want to check.
With the route approach you don't have to bother with anything in your component, when Angular reaches it you can be 100% sure that all required states are ready.
States Setup
For this feature to work properly the states must be set in the configuration file like this:
state: {
database: {
transactions: { // <- State name (same name used in the state class in the @State() decorator)
state: TrasactionsDbState, // <- State class
loadAction: TransactionsDbActions.LoadAll, // <- Action used to load the state
clearFunction: getTransactionsInitialState, // <- Function to restore this state to it's initial value
cacheTimeout: 5 // <- Cache timeout (in minutes)
},
categories: {
state: CategoriesDbState,
loadAction: CategoriesDbActions.LoadAll,
clearFunction: getCategoriesInitialState,
cacheTimeout: 1
},
accounts: {
state: AccountsDbState,
loadAction: AccountsDbActions.LoadAll,
clearFunction: getAccountTypesInitialState,
cacheTimeout: 1
},
accountTypes: {
state: AccountTypesDbState,
loadAction: AccountTypesDbActions.LoadAll,
clearFunction: getAccountsInitialState,
cacheTimeout: 1
},
},
// The feature state configuration only requires the state and clear function property
feature: {
categoriesManager: {
state: CategoriesManagerState,
clearFunction: getCategoriesManagerInitialState
},
accountsManager: {
state: AccountsManagerState,
clearFunction: getAccountsManagerInitialState
},
}
},
IMPORTANT:this feature will only work if the states used as requirements for the routes have alastUpdatedproperty that is initialized withnulland set with the current time when it's first populated.
Authentication and authorization
Route authentication guard
The library provides an authentication route guard called RbkAuthGuard.
The checks made by the guard are the following:
if the route does not need any special claim, return the value in the
AuthenticationSelectors.isAuthenticatedselectorif the route needs any special claim, it should be passed to the router like this:
const routes: Routes = [ { path: 'authenticated-route', component: AuthenticatedRouteComponent, canActivate: [ RbkAuthGuard ], data: { title: 'My Route', breadcrumb: 'My Route', claim: 'CAN_ACCESS_MY_AUTHENTICATED_ROUTE' }, children: [ ... ] }, ];when the route needs any special claim, the guard will search for that claim in the user data, if the user is authenticated and he has the needed claim the access is allowed.
when the route needs any special claim and the guard doesn't find that claim directly in the user data, it will try to find a claim in the format
{{domain}}|{{claim}}. The domain is read from thedomainproperty in the user data. If you need to use this domain feature, you need to set it in theaccessTokenClaimsproperty in the config file.
rbkCanAccess pipe
This pipe is used to restrict access of HTML components using *ngIf. For instance, the following sample renders the div only if the user has the IS_ADMIN claim in the JWT token.
export class MyComponent {
public isAdminClaim = 'IS_ADMIN';
constructor() {
}
}<div *ngIf="!(isAdminClaim | rbkCanAccess)">
</div>Global error handling
All http errors are automatically handled by the library through interceptors. This behavior is set in the config file by the httpBehaviors property and can be overrided individually for each request, like this:
return this.http.post(url, body,
this.generateDefaultHeaders({
errorHandlingType: 'toast',
}));There are three possible values: toast, dialog and none.
Every time request returns an error, all the states are emmited again, in their current state, force UI refresh. This behavior can be overrided for each individual request using the restoreStateOnError property in the httpBehaviors section of the config file.
Loading flag
The library offers three ways of handling in progress http requests:
GLOBAL: when am http request is started, the loading flag in the store is set. If another request is started before the previous is finished, the flag will remain set. Only when the last request is finished the flag will be set to false. This flag can be observed using the
ApplicationSelectors.globalIsLoadingselector.LOCAL: with this option an individual flag is set for each request and they can be observed using the
ApplicationSelectors.isWaitingRequestselector, passing theidused in the http request. The following sample shows how to use this option for a give request:
return this.http.post(url, body,
this.generateDefaultHeaders({
loadingBehavior: 'local',
localLoadingTag: 'my-request-id'
}));- NONE: none of the store loading flags will be set.
You can set the default values for all http requests in the httpBehaviors property of the config file. But this value can be individually overriden on each request like in the examples above.
Http request behaviors
To fully use all the features of the library, all the http services must inherit from the BaseApiService. This way all requests will use the default values set in the config file, but if any value needs to be overriden it should be done using the generateDefaultHeaders method of te base class. The following sample shows how to do this:
return this.http.post(url, body,
this.generateDefaultHeaders({
errorHandlingType: 'dialog',
loadingBehavior: 'local',
localLoadingTag: 'my-request-id',
compression: true,
authentication: true,
needToRefreshToken: true
}));Note that there is no need to set all values, only the ones that are actually needed.
Title service
This service is used to set the title that will be displayed in the browser tab. The title needs to be set in the route, and if it's not set the service will try to infere it from the route name. The following example show how the title is set in the route.
{
path: 'my-route',
component: MyComponent,
data: { title: 'My Title' },
}The example above will set the tab title to XX > My Title, in which the XX is the value of the applicationName property in the config file.
The TitleService is already injected by the library.
Breadcrum service
This service constructs a breadcrum object to be used in the application breadcrum (really!). It's used the same way as the TitleService and it uses the same property of the route.
The BreadcrumService is already injected by the library.
UI utilities
rbkTableClearExtension: this directive is used to help with the cleaning of filters inTableandTreeTablecomponents. It extends them by appending theclearmethods to them. It must be used in the table and every filter input that needs to be cleared must use therbkInputClearExtensiondirective. Below is a sample of how it should be used.
<p-treeTable #dt [tableClearExtension]="dt" ...>
<ng-template pTemplate="colgroup">
...
</ng-template>
<ng-template pTemplate="header">
<tr>
...
</tr>
<tr>
<th><input #input1 [inputClearExtension]="{ table: dt, component: input1 }" pInputText type="text" (input)="dt.filter($event.target.value, 'productHtml', 'contains')"></th>
<th><p-multiSelect #input2 [inputClearExtension]="{ table: dt, component: input2 }" (onChange)="dt.filter($event.value, 'details.warehouse.id', 'simplenamed')" appendTo="body"></p-multiSelect></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowNode let-rowData="rowData">
...
</ng-template>
</p-treeTable>Utility functions
replaceArrayItem:: replaces an item in a given array by other with the sameid. The items must have anid. It's used to help with state updates.isWithinTime:: verifies if the data of a specific database store is still valid given the maximum age in minutes.orderArrayByProperty:: sort an array of objects by a given property.isEmpty:: checks wheter a string isnull,undefinedor''.clearArray:: removes all elements of an array.flattenObject:: when dialog data is returned by thengx-smzdialogs, it may have many properties of typeSimpleNamedEntity, and this data is usually used to be send to an API, which probably needs a propertyemployeeIdof typestringinstead of a propertyemployeeof type{ id: string, name: string}. This function does this kind of transformation in the object, it finds all object propertyes that have anidproperty and converts it to{{propertyName}}id. It also has a generic type to cast the data to the specified type.
The following example shows the input of the function the what the output would be.
// Use (result will be of type 'MyType')
const result = flattenObject<MyType>(input);
// input
{
id: '2',
color: '#4d3a8f',
category: {
id: '9',
name: 'My category',
icon: 'fa fas-home'
},
order: 19,
parent: {
id: '3',
name: 'My parent'
}
}
// output
{
id: '2',
color: '#4d3a8f',
categoryId: '9',
order: 19,
parentId: '3'
}fixDateProperties:: converts any date property stored as string or number to a Date object. The property name must starts withdateor ends withDate. The operation is done in the same object passed to the function as a parameter. If the date is stored as epoch it can be either in seconds or miliseconds.
RXJS custom operators
fixDates: this operator do the same as thefixDateProperties, but wrapped in a more convenient way for objects coming from an http request.
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago