NgModules, lazy loading and fun with bundle analyzing

by Aliaksei Kuncevic

NgModule vs ES6 Module

ES6 Module

import { AppComponent } from './app.component';
export { pickBy, cloneDeep }

NgModule

@NgModule({
  imports: [
    CommonModule,
    CustomMaterialModule,
    ...
  ],
  exports: [
    CommonModule,
    CustomMaterialModule,
    Components.AccountComponent,
    Components.DateTimeComponent,
    ...
  ],
  declarations: [
    Components.AccountComponent,
    Components.DateTimeComponent,
    ...
  ],
  providers: []
})
export class SharedModule { }
})

Module types

  • App module
  • Core module
  • Shared module
  • Feature module
  • Custom module

Disorganized AppModule

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule, Http } from '@angular/http';
import { RouterModule, RouterLink } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';
import { MaterialModule, MdIconRegistry, MdSelectModule } from '@angular/material';
import { FlexLayoutModule } from '@angular/flex-layout';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { EffectsModule } from '@ngrx/effects';
import { RouterStoreModule } from '@ngrx/router-store';
import { ContextMenuModule } from 'angular2-contextmenu';

import { AppRoutingModule  } from './app-routing.module';
import { AppComponent } from './app.component';
import { AppAlertService } from './services/app-alert.service';
import { AuthService } from './services/auth.service';
import { ExtendedHttpService } from './services/extended-http.service';

import { reducer } from './redux/reducers';
import { RoundingRuleEffects } from './redux/effects/rounding-rule';
import { SiteEffects } from './redux/effects/site';
import { DialogModule } from './components/dialogs/app-dialog.module';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { DateTimeComponent } from './components/partials/date-time/date-time.component';
import { TimeService } from './services/time.service';
import { GreetingsComponent } from './components/partials/account/greetings/greetings.component';
import { LogoComponent } from './components/partials/logo/logo.component';
import { LoggedInGuard } from './guards/logged-in.guard';
import { PermissionGuard } from './guards/permission.guard';
import { LoginComponent } from './components/login/login.component';
import { AccountComponent } from './components/partials/account/account.component';
import { LoaderComponent } from './components/partials/loader/loader.component';
import { InsufficientPermissionsComponent } from './components/insufficient-permissions/insufficient-permissions.component';
import { PrettyPrintPipe } from './pipes/pretty-print.pipe';
import { SitePickerComponent } from './components/partials/site-picker/site-picker/site-picker.component';
import { RulesModule } from './components/rules/rules.module';
import { PowerbiModule } from './components/reports/powerbi/powerbi.module';
import { CustomReportsModule } from './components/reports/custom/custom-reports.module';
import { HomeModule } from './components/home/home.module';
import { LoaderService } from './services/loader.service';

@NgModule({
  declarations: [
    AppComponent,
    NotFoundComponent,
    DateTimeComponent,
    GreetingsComponent,
    LogoComponent,
    LoginComponent,
    AccountComponent,
    LoaderComponent,
    InsufficientPermissionsComponent,
    PrettyPrintPipe,
    SitePickerComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule,
    ReactiveFormsModule,
    MaterialModule,
    FlexLayoutModule,
    DialogModule,
    ContextMenuModule,
    BrowserAnimationsModule,
    HomeModule,
    CustomReportsModule,
    PowerbiModule,
    RulesModule,
    StoreModule.provideStore(reducer),
    RouterStoreModule.connectRouter(),
    StoreDevtoolsModule.instrumentOnlyWithExtension(),
    EffectsModule.run(RoundingRuleEffects),
    EffectsModule.run(SiteEffects),

  ],
  providers: [
    MdIconRegistry,
    MdSelectModule,
    AppAlertService,
    TimeService,
    LoggedInGuard,
    PermissionGuard,
    RouterLink,
    AuthService,
    { provide: Http, useClass: ExtendedHttpService },
    LoaderService
    ],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor() {

  }
} 
})

110 Lines of code and can grow...

disorganized Folders Sturecture

Core, Feature, Shared, Custom

Organized AppModule

import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { ComponentsModule } from './components';
import { AppRoutingModule  } from './app-routing.module';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    AppRoutingModule,
    ComponentsModule,
    CoreModule,
    SharedModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor() { }
}
	

21 Lines of code

Style Guide #04-06

Organized Folders Sturecture


Style Guide #04-06

Lazy loading

  • We are loading feature modules
  • No references exposed from feature module
  • Use `loadChildren` proerty with `text link` to your module
  • Feature routing module + feature module

Lazy loading LoadChildren


  import { MyComponent } from './components';
  ...
	
  {
    path: 'stuff',
    component: MyComponent
  }
	

  {
    path: 'stuff',
    loadChildren: 'app/features/my-feature/my-feature.module#MyModule'
  }
	

Lazy loading Routes

const routes: Routes = [
{
  path: '',
  children: [
  {
    path: '',
    component: MyComponent
   },
  ]
}
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MyRoutingModule { }
	

Lazy loading Module

@NgModule({
  imports: [
    CommonModule,
    PowerbiRoutingModule,
    FormsModule,
    NgxDatatableModule,
    ReactiveFormsModule,
    FormsModule,
    HttpModule,
    SharedHttpModule.forRoot(),
    CustomMaterialModule
  ],
  declarations: [
    MyComponent,
  ]
})
export class MyModule { }
}

Module.ForRoot() pattern

@NgModule({
  imports: [
    CommonModule,
    HttpModule
  ],
  exports: [
    CommonModule,
    HttpModule
  ]
})
export class MyModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: MyModule,
        providers: [
          service1,
          service2
        ]
    };
  }
}

Demo

Tools

  • webpack-bundle-analyzer
    ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json
    							 
  • source-map-explorer
    source-map-explorer bundle.min.js bundle.min.js.map
    							 

Bafore vs After

Things to notice

import pickBy from 'lodash';
import * as moment from 'moment';
import { Observable } from 'rxjs';
import pickBy from 'lodash-es/pickBy';
import * as moment from 'moment-mini';
import { Observable } from 'rxjs/Observable';

Custom module

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  MdDialogModule,
  MdToolbarModule

} from '@angular/material';

@NgModule({
  imports: [
    CommonModule,
    MdDialogModule,
    MdToolbarModule
  ],
  exports: [
    CommonModule,
    MdDialogModule,
    MdToolbarModule
  ]
})
export class CustomMaterialModule { }
	
Issue #4137

RxJS imports

rxjs-operators.ts
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

Before

import 'rxjs/add/operator/map';
MyObservable$.map((cities: City[]) =>
  new city.LoadSuccessAction(cities)
)

After

MyObservable$.map((cities: City[]) =>
  new city.LoadSuccessAction(cities)
)

Lodash imports

lodash-functions.ts
import pickBy from 'lodash-es/pickBy';
import cloneDeep from 'lodash-es/cloneDeep';
export {
  pickBy,
  cloneDeep
}

Usage

import { pickBy } from './../../../core/lodash-functions';
...
return pickBy(item, function(value, key) { return !key.startsWith('$$'); }) as T;