The new version for this was in progress.
As requested from many members of CodeProject, We have new version of TinyERP (using angular 2 and typescript) released.
For more information about other features of TinyERP, Please visit "TinyERP - Overview".
New changes
- new version of Angular (Angular 2 with typescript)
- Load Module on demand
- Completely isolated module
- Multiple layouts
- Wrap the response from server side.
How to run the app?
the build script (gulp/ webpack) will be added later.
For now, to run please app, please follow the instruction as below:
- Check out the code from github (https://github.com/techcoaching/TinyERP)
- To run the API: This was the same as previous version, please have a look at TinyERP - Part 8 - Build & Deploy Application
- To run the client:
- Run "npm install" for installing nodejs packages from <root folder path>(folder of client code):
- Run tsc (or npm run tsc) to compile typescript code into javascript:
- Run "npm start" to run the app from nodejs:
- Open your browser and access to "http://localhost:3000" and see the below:
- You can try to click on "Security> Permission" to see the list of permissions or add new permission into the list:
Load Module on demand
At the first time, the app was launched. Open the console of your browser, You will see:
There are some remarkable from the photo above:
- The URL is http://localhost:300, there is not page was loaded at this time.
- Look at the "source" tab (on chrome), We can only see the common module was loaded on the browser. Where is security and setting modules?
Now, let try to click on "Security > Permission" and see the list of Permissions:
Look again, we see:
- The url is "http://localhost:3000/security/permissions", this is the route config for permission pages. it is ok.
- And we see the list of permissions were displayed. not weird.
- In the "source" tab your your browser, the security was loaded automatically. woa woa, How can this happen?
this is really important as in enterprise app (ERP context), your application may have 15 or 20 modules. So:
- we did not need to load all of them to the client on the first run.
- Most users only use a very small set of functions in your app. So load all modules to client is unnecessary.
- This also speed up performance of the app.
- Module only was loaded when user need.
Set default module:
In most case, we want the app display default feature to user when they access to the app.
Let set "isDefault" to true in "<root folder>/src/config/module.ts" as below:
let modules: Array<IModuleConfigItem> = [
{ name: ModuleNames.Security, urlPrefix: ModuleNames.Security, path: ModuleNames.Security, isDefault: true },
{ name: ModuleNames.Setting, urlPrefix: ModuleNames.Setting, path: ModuleNames.Setting}
];
Default value for "isDefault" is false.
So, start the app again with this configuration, the application will be launched as below:
In this case:
- The browser auto navigate to "security/permissions" uri as default.
- The list of permissions were rendered on browser as default.
- And in the console, Security module was loaded.
Ok, what about "Completely isolated module"?
Now, open the client code in visual code, we have:
- "src/modules": this contains all modules of your application. Each module will be organized inside 1 sub-folder, similar as security, setting, ... Each module will not know about other. It means in security module, we will not know anything about setting module.
- "src/modules/common": this is a special that can be called anywhere in the app. We can call this is share resource.
- "src/modules/security": as mentioned above, this contains all features of security provided, In security, we can call function, use public component from common module only.
- "src/modules/setting": this was the same as security module.
Now, let try to check dependency between modules in your app:
- Open you source code in window explorer and delete the whole "src/modules/setting" folder:
- Run "tsc" (or npm run tsc) to compile the code again, there is no error:
- Now, run the app with "npm start":
We can see the app can run without any error.
- Let try to access to specified url of setting module (for example: http://localhost:3000/setting/contentTypes), the app will get error as it was not understand what is "setting/contenttypes".
- We still see the menu of setting on the right, remove those configurations in "<root>/src/config/menus.ts", currently the menu of modules does not belong to that module. We will improve this later:
import { AppMenuItem } from "@app/common";
let menus: Array<AppMenuItem> = [
new AppMenuItem("Security", "", "fa fa-home", [
new AppMenuItem("Permissions", "/security/permissions")
]),
new AppMenuItem("Setting", "", "fa fa-edit", [
new AppMenuItem("ContentTypes", "/setting/contentTypes")
])
];
export default menus;
In the enterprise application, manage dependency between module is important. If this was not managed well, changing in 1 module can break working feature in other module and lead to many potential issues.
What is "Multiple layouts"?
In application, we may provide multiple layouts feature, where user can select which layout (main menu on the top or on the right side, ect...) they prefer to use dynamically.
It is confusing between layout and theme.
- Multiple theme: it means the app has the same information displayed, just in different look and feel
- Multiple layout: such as: 2 column or 3 columns layout...
Update appropriated layout in "<root>/src/config/appConfig.ts" as below:
import { DefaultLayoutModule, DefaultLayout } from "@app/themes/default";
let appConfig: IAppConfig = {
menus: ...,
localization: {
...
},
localeUrl: "...",
rootApi: "...",
ioc: ...,
layout: {
component: DefaultLayout,
module: DefaultLayoutModule
},
routes: ...
}
export default appConfig
To understand how to define new layout, please have a look at:
- Module: "<root>/src/themems/default/defaultLayoutModule.ts"
- Layout: "<root>/src/themems/default/defaultLayout.ts"
Please aware that the selector of layout must be "layout", this was used from index.html file
Wrap the response from server side
We improve how the API return the response to client, this will remove duplicated code in previous version.
Before, we have this code in PermissionController:
namespace App.Api.Features.Security
{
[RoutePrefix("api/permissions")]
public class PermissionsController : ApiController
{
[HttpGet]
[Route()]
public IResponseData<IList<PermissionAsKeyNamePair>> GetPermissions()
{
IResponseData<IList<PermissionAsKeyNamePair>> response = new ResponseData<IList<PermissionAsKeyNamePair>>();
try
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
IList<PermissionAsKeyNamePair> pers = permissionService.GetPermissions();
response.SetData(pers);
}
catch (ValidationException ex)
{
response.SetErrors(ex.Errors);
response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed);
}
return response;
}
[HttpGet]
[Route("{id}")]
public IResponseData<GetPermissionResponse> GetPermission([FromUri]Guid id)
{
IResponseData<GetPermissionResponse> response = new ResponseData<GetPermissionResponse>();
try
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
GetPermissionResponse per = permissionService.GetPermission(id);
response.SetData(per);
}
catch (ValidationException ex)
{
response.SetErrors(ex.Errors);
response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed);
}
return response;
}
}
}
We have so many redundant code, for example, with GetPermissions:
public IResponseData<IList<PermissionAsKeyNamePair>> GetPermissions()
{
IResponseData<IList<PermissionAsKeyNamePair>> response = new ResponseData<IList<PermissionAsKeyNamePair>>();
try
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
IList<PermissionAsKeyNamePair> pers = permissionService.GetPermissions();
response.SetData(pers);
}
catch (ValidationException ex)
{
response.SetErrors(ex.Errors);
response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed);
}
return response;
}
Totally 16 lines of code for this function, and only have 3 lines of code handle the logic of this function, and the try/catch bock does not create the value for this function. So we can ignore this
In new version this will be changed to:
public IList<PermissionAsKeyNamePair> GetPermissions()
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
return permissionService.GetPermissions();
}
You can see, we only have 4 lines of code, and this is exactly what the caller want. so the code look more elegant.
The whole PermissionsController looks like below:
[RoutePrefix("api/permissions")]
public class PermissionsController : BaseApiController
{
[HttpGet]
[Route("")]
[ResponseWrapper()]
public IList<PermissionAsKeyNamePair> GetPermissions()
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
return permissionService.GetPermissions();
}
[HttpGet]
[Route("{id}")]
[ResponseWrapper()]
public GetPermissionResponse GetPermission([FromUri]Guid id)
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
return permissionService.GetPermission(id);
}
[HttpPost]
[Route("")]
[ResponseWrapper()]
public CreatePermissionResponse CreatePermission(CreatePermissionRequest request)
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
return permissionService.Create(request);
}
[HttpPut]
[Route("{id}")]
[ResponseWrapper()]
public void UpdatePermission([FromUri]Guid id, UpdatePermissionRequest request)
{
request.Id = id;
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
permissionService.Update(request);
}
[HttpDelete]
[Route("{id}")]
[ResponseWrapper()]
public void DeletePermission([FromUri]Guid id)
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
permissionService.Delete(id);
}
}
Please write me a comment if you have any suggestion or any question.
For more information about other articles in this series
Thank you for reading,CodeProject
Note: Please like and share to your friends if you think this is useful article, I really appreciate