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
- https://angular.io/tutorial/toh-pt4
- https://angular.io/guide/architecture-modules
- https://angular.io/guide/ngmodules
- https://angular.io/guide/ngmodule-faq
- https://angular.io/guide/architecture-services
- https://angular.io/guide/dependency-injection
- https://angular.io/guide/dependency-injection-providers
- https://angular.io/guide/dependency-injection-in-action
- https://www.freelancermap.com/freelancer-tips/12255-forroot-forchild-angular
-
https://pazel.dev/how-to-keep-your-secrets-from-your-source-code-in-an-angular-project
Thank you!
Angular. Modules & Services. HTTP
By Pavel Razuvalau
Angular. Modules & Services. HTTP
- 597