Angular

Modules

Services

ES6 Modules

function baz() {
    // Some logic here
}

function qux() {
    // Another magic here
}

export { baz, qux }
export function foo() {
    // Some logic here
}

export function bar() {
    // Another magic here
}
import { foo, bar } from 'helpers1.js';
import { baz, qux } from 'helpers2.js';

const appComponent = {};

foo();
bar();

export { appComponent, baz, qux }

@NgModule

a TypeScript class with @NgModule annotation

Identifies it's own components, directives, pipes, services

Makes some of them public

Why to use @NgModule?

  • Better business logic organization
  • Re-usability
  • Maintainability
  • Performance (lazy-loading)

Types of @NgModules

  • Library modules - Angular (BrowserModule, FormsModule, HttpModule, RouterModule, ...), 3rd-party modules
  • Features - related business logic that can be packaged into a single concern.
  • Core - module that contains root logic (mostly services).
  • Widget/Shared - components you use everywhere. Loading spinners, social media icons, etc.
  • Routing - used to manage routes. Should not declare anything.

App module

Project modules structure

Libraries

CoreModule

Feature

Feature

Shared

Angular, etc

App module

Project modules structure

Libraries

CoreModule

Feature

Feature

Router

With lazy loading

Angular, etc

Shared

@NgModule structure

AppModule

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [ AppComponent ],
  imports: [ BrowserModule ],
  providers: [],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.log(err));

@NgModule metadata

@NgModule({
  declarations: [ AppComponent ],
  imports: [ BrowserModule ],
  exports: [ ... ]
  providers: [ ... ],
  bootstrap: [ AppComponent ]
})
  • declarations - Declare components, directives, and pipes to make them privately available in this module.

  • imports - This is where you import other modules.
  • exports - Makes the declared view public so they can be used by other modules.
  • providers - Defines services that can be injected into this module’s views.
  • bootstrap - The component used to launch the app, the AppComponent

exports, imports...

@NgModule({
  declarations: [ a1, a2, a3 ],
  exports: [ a1, a2 ]
})

Module A

@NgModule({
  imports: [ ModuleA ]
  declarations: [ b1, b2 ],
  exports: [ b1 ]
})

Module B

@NgModule({
  imports: [ ModuleB ]
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ]
})

AppModule

Scope

a1, a2, a3

a1, a2, b1, b2

b1, AppComponent

exports, imports...

@NgModule({
  declarations: [ a1, a2, a3 ],
  exports: [ a1, a2 ]
})

Module A

@NgModule({
  imports: [ ModuleA ]
  declarations: [ b1, b2 ],
  exports: [ b1, ModuleA ]
})

Module B

@NgModule({
  imports: [ ModuleB ]
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ]
})

AppModule

Scope

a1, a2, a3

a1, a2, b1, b2

a1, a2,

b1, AppComponent

providers...

@NgModule({
  providers: [ s1, s2 ]
})

Module A

@NgModule({
  imports: [ ModuleA ]
  providers: [ s3 ],
})

Module B

@NgModule({
  imports: [ ModuleB ],
  providers: [ s4 ],
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ]
})

AppModule

Root Injector

s1, s2, s3, s4

s1, s2, s3

Scope

s1, s2

@NgModule Configuration

@NgModule({ ... })
export class GreeingModule {
  static forRoot(config: UserServiceConfig): ModuleWithProviders {
    return {
      ngModule: GreetingModule,
      providers: [
        { provide: UserServiceConfig, useValue: config }
      ]
    };
  }
}
@NgModule({
  imports: [
    BrowserModule,
    ContactModule,
    GreetingModule.forRoot({userName: 'Miss Marple'}),
    AppRoutingModule
  ],
  declarations: [
    AppComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Services & DI

Class without DI

export class Car {
    public engine: Engine;
    public wheels: Wheel[];
    private readonly description: 'without DI';

    constructor() {
        this.engine = new Engine();
        this.wheels = new Array(4).fill(null).map(() => new Wheel());
    }

    drive() {
        return `${this.description}: car with ` +
            `${this.engine.volume} engine and ` + 
            `${this.wheels[0].material} wheels`;
    }
}

Class with DI

export class CarWithDI {
    private readonly description: 'with DI';

    constructor(public engine: Engine, public wheels: Wheels) {}

    drive() {
        return `${this.description}: car with ` +
            `${this.engine.volume} engine and ` + 
            `${this.wheels[0].material} wheels`;
    }
}
let car = new CarWithDI(getInstance(Engine), getInstance(Wheels))

getInstance - gets existing instance of creates a new one

Services

  • Contains any value, function, or feature that an app needs
  • Should do something specific and do it well
  • Singleton

Services

Must be provided in order to use it

import { Injectable } from '@angular/core';

@Injectable()
export class FooService {

  bar() {
    // Some magic happens
  }
}
import { Component } from '@angular/core';
import { FooService } from '../services/foo-service';

@Component({ ... })
export class FooComponent {
    constructor(private fooService: FooService) {}
}
import { NgModule } from '@angular/core';

@NgModule({
  declarations: [ ... ],
  imports: [ ... ],
  providers: [ FooService ],
  bootstrap: [ FooComponent ]
})
export class FeatureModule { }

Dependency Providers

import { NgModule } from '@angular/core';
import { FooService } from 'services/foo-service';

@NgModule({
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    FooService,
    { provide: 'SecondInstance', useClass: FooService }
  ],
  bootstrap: [ FooComponent ]
})
export class FeatureModule { }
import { Component, Inject } from '@angular/core';
import { FooService } from '../services/foo-service';

@Component({ ... })
export class FooComponent {
    constructor(private fooService: FooService,
                @Inject('SecondInstance') private secondFooService: FooService) {}
}

useExisting

import { NgModule } from '@angular/core';
import { FooService } from '../services/foo-service';

@NgModule({
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    FooService,
    { provide: 'SecondToken', useExisting: FooService }
  ],
  bootstrap: [ FooComponent ]
})
export class FeatureModule { }
import { Component, Inject } from '@angular/core';
import { FooService } from '../services/foo-service';

@Component({ ... })
export class FooComponent {
    constructor(@Inject('SecondToken') private secondFooService: FooService) {}
                
}

UseValue & UseFactory

import { NgModule } from '@angular/core';
import { FooService } from 'services/foo-service';
import { AnotherImplementationService} from 'services/another-service';

function myFactory(IS_PROD: boolean) {
  return IS_PROD ? new FooService() : new AnotherImplementationService();
}

@NgModule({
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    { provide: 'IS_PROD', useValue: true },
    { provide: FooService, useFactory: myFactory, deps: ['IS_PROD']}
  ],
  bootstrap: [ FooComponent ]
})
export class FeatureModule { }

InjectionToken

import { NgModule, InjectionToken } from '@angular/core';
import { FooService } from 'services/foo-service';
import { AnotherImplementationService} from 'services/another-service';

const IS_PROD = new InjectionToken<boolean>('isProd');

function myFactory(IS_PROD: boolean) {
  return IS_PROD ? new FooService() : new AnotherImplementationService();
}

@NgModule({
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    { provide: IS_PROD, useValue: true },
    { provide: FooService, useFactory: myFactory, deps: ['IS_PROD']}
  ],
  bootstrap: [ FooComponent ]
})
export class FeatureModule { }

Optional Dependencies

import { Injectable, Optional } from '@angular/core';
import { FooService } from './data-service';

@Injectable()
export class MyDataService {
  public data: any;

  constructor(@Optional() private dataService: DataService) {
    this.data = this.dataService ? this.dataService.getData() : 'local';
  }

  getData() {
    return this.data;
  }
}

Useful links

Q & A

Angular. Modules & Services

By Pavel Razuvalau

Angular. Modules & Services

  • 866