Angular

Modules

Services

HTTP

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 { }

provideIn

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
}
import { Injectable } from '@angular/core';
import { UserModule } from './user.module';

@Injectable({
  providedIn: UserModule,
})
export class UserService {
}

The example above shows the preferred way to provide a service in a module. This method is preferred because it enables tree-shaking of the service if nothing injects it.

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;
  }
}

HTTP

Simple Usage

1. Import it to the application module:

import { HttpClientModule } from '@angular/common/http'
imports: [
  BrowserModule,
  HttpClientModule
],

2. Inject HttpClient into your service:

constructor(private http: HttpClient) {
}

3. Use it:

public getUsers(): Observable<User[]> {
  return this.http.get<User[]>(`${BASE_URL}`);
}

4. Call an API inside of your component:

this.usersService.getUsers().subscribe(
    (response) => ...,
    (error) => ...,
    () => ...
)

API Calls With Params

Pass count and text fragment as a query params

return this.http.get<User[]>(`${BASE_URL}`, {
    params: {textFragment, count}
});

Full get method description:

get(url: string, options?: {
    headers?: HttpHeaders | {
        [header: string]: string | string[];
    };
    observe?: 'events' | 'response' | 'body';
    params?: HttpParams | {
        [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType?: 'arraybuffer' | 'blob' | 'text' | 'json';
    withCredentials?: boolean;
}): Observable<any>;

GET (DELETE, HEAD, OPTIONS)

API Calls With Params

Create a new hero

return this.http.post<Hero>(`${BASE_URL}`, hero, httpOptions);

Full post method description:

post(url: string, body: any, options?: {
    headers?: HttpHeaders | {
        [header: string]: string | string[];
    };
    observe?: 'events' | 'response' | 'body';
    params?: HttpParams | {
        [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType?: 'arraybuffer' | 'blob' | 'text' | 'json';
    withCredentials?: boolean;
}): Observable<any>

POST (PUT, PATCH)

Error Handling

Handle error in the component:

public search(queryString: string): void {
    this.usersWithParamsSubscription = this.usersService
        .getUsersWithParams(queryString, this.countToLoad)
        .subscribe(
            (res: User[]) => {
                this.users = res;
            },
            (error: HttpErrorResponse) => console.log(error)
        )
}

Handle error in the service and retry:

public getUsersWithParams(textFragment: string, count: string): Observable<User[]> {
    return this.http.get<User[]>(`${BASE_URL}`, { params: { textFragment, count } })
    .pipe(
        retry(4),
        catchError(this.handleError)
    );
}

Interceptors

Describe interceptor as a TypeScript class with @Injectable decorator: 

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // Clone the request and replace the original headers with
        // cloned headers, updated with the authorization.
        const authReq = req.clone({
            headers: req.headers.set('Authorization', 'Bearer token')
        });

        // Pass the request to the next handler
        return next.handle(authReq);
    }
}

Provide your interceptor: 

{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },

Useful links

Thank you!

Copy of Angular. Modules & Services. HTTP

By Pavel Razuvalau

Copy of Angular. Modules & Services. HTTP

  • 300