With angular-cli tool entering RC-1, I decided to start migrating my open source project ”Echoes Player” from angular class boilerplate. Some of the code in “Echoes Player” wasn’t AOT compatible. As a result, compilation logged errors to the console. In this post I’m sharing guidelines for making your ngrx related code compatible with AOT.
The AngularClass boilerplate is a great all in one source for starting a project with Angular. It includes many features and abilities that makes development with Typescript and Angular very comfortable and easy to use.
Angular-cli is a command line tool for starting a new Angular app - its creates a new local repository while providing an interface for adding components, directives, services and all other Angular entities through command line commands. Instead of cloning a repository from github, the cli tool provides a one liner method for starting a project.
With this tool growing popularity, I decided to experiment with it and understand how to work with it, so I can better understand how teams may benefit form using it as the tool of choice for starting and maintaining an Angular project.
AOT stands for Ahead Of Time compilation. The AOT compiler, creates a statically ready code to run in the browser. This means that when the application runs in the browser, the compilation phase is not needed. In contrary, compiling without AOT is JIT - Just In Time - where code is compiled at runtime, within the browser.
There are few benefits for compiling with AOT:
Compiling with AOT is triggered via a terminal/cmd command. The ”@angular/compiler-cli” and the ”@angular/platform-server” packages are required. Since i’m using the “angular-cli” tool in my project, these are already included.
To compile, this command should run in the terminal:
To compile for production and get the benefits of minifying and others, you should run:
Please note that the flag -prod
includes compiles with AOT (thanks for the update @shiny)
Now, lets overview the guidelines that we should follow when working with ngrx/store and ngrx/effects, which will allow AOT compilation to compile without any errors.
In “Echoes Player”, I chose to sync the store’s state to the localstorage. In order to achieve that, I installed the “ngrx-store-localstorage” npm package, which exports the ”localStorageSync()” function. This function is supposed to be used as a middleware reducer which saves the current store’s state to the localstorage with any dispatched action.
In order to combine reducers, the ”compose()” function from the “@ngrx/core/compose” should be used along with the ”combineReducers” from “@ngrx/store”. In the end, the compose return value should be stored in a variable:
const reducers = {
player,
nowPlaylist,
user,
search,
appLayout,
};
const appReducer = compose(localStorageSync(Object.keys(reducers), true), combineReducers)(reducers);
Normally, the ”appReducer” which holds a reference the new composed reducer, should be used as the argument for “StoreModule.provideStore(productionReducer)” to bootstrap the store. However, that approach is not compatible with AOT. In order to use ”compose” in a way that is compatible with AOT, the “appReducer” is required to be wrapped with a function which will be sent as an argument to the ”provideStore()”. This is required for the AOT compiler to statically analyze the code and compile and produce the AOT ready code.
const actions = []; // array of app's action classes
const reducers = {
player,
nowPlaylist,
user,
search,
appLayout,
};
const appReducer = compose(localStorageSync(Object.keys(reducers), true), combineReducers)(reducers);
// This is required for AOT
export function reducer(state: any, action: any) {
return appReducer(state, action);
}
@NgModule({
imports: [
StoreModule.provideStore(reducer)
],
declarations: [],
exports: [],
providers: [ ...actions ]
})
Reducers are meant to be pure functions. In previous versions of blog posts and other sources, reducer functions were demonstrated as anonymous function assignments to variables, sometimes using function arrows as well:
As a rule of thumb for AOT in general (and not just for ngrx), exported arrow functions cannot be used. The AOT compatible way for defining reducer functions is with an exported named function declaration.
I wrote about using ngrx/effects as a layer for async logics and more complex logic. Adding an Effect class to Angular is run separately for each effect using ”EffectsModule.run()” which creates a provider for each effect . Since in “Echoes Player” there are few effects classes, I chose to use a dynamic creation using a simple functional “map”:
@NgModule({
imports: [CoreStoreModule, effects.map(effect => EffectsModule.run(effect))],
})
export class CoreModule {}
Since both arrow functions and dynamic creation within a decorator are not compatible with AOT, I found (with the help of the community in the github repo of effects) that currently the solution is to run each effect separately while creating an array of effect providers, then, spread this array to the “imports” array:
const AppEffectModules = [
EffectsModule.run(AppEffects[0]),
EffectsModule.run(AppEffects[1]),
EffectsModule.run(AppEffects[2]),
EffectsModule.run(AppEffects[3]),
]
@NgModule({
imports: [CoreStoreModule, ...AppEffectModules],
})
export class CoreModule {}
I has a very positive experience from the migration process of moving to angular-cli. This turned out to be an opportunity to learn new skills:
As of the time of writing this post, I haven’t switched to using angular-cli completely yet. I have some thoughts in mind for finding a simpler way of moving between repositories/tools.
You can view the history of commits of the migration process to angular-cli.
If you’re looking for Angular Consulting / Front End Consulting, please consider to approach via the promotion packages below (no strings attached):