Angular, Best Practices, htmlhint, stylelint, prettierrc, proxy
In order to deliver a hight quality angular application , you have to follow a series of guidelines and good practices.
this file specifies the root files and the compiler options for an angular project.
Then add the command to script section of package.json
"lint:ts":"eslint --color -c .eslintrc --ext .ts ."
install htmlhint
npm install --save-dev htmlhint
create a .htmlhintrc configuration file in the root of your project.
{
"attr-value-not-empty": false
}
run htmlhint on, for example, all the html files in your project.
"lint:html": "npx htmlhint \"src\" --config .htmlhintrc ",
configure rules baesd on rules list of htmlhintrc
install stylelint
npm install --save-dev stylelint stxlelint-config-standard
create .htmlhintrc and add
{
"extends": "stylelint-config-standard"
}
run htmlhint
"lint:scss": "npx stylelint \"src/**/*.scss\" --syntax scss",
configure .prettierrc
create a file proxy.config.json next to package.json
add the following content
{
"/folder/sub-folder/*": {
"target": "http://localhost:3000",
"secure": false,
"pathRewrite": {
"^/folder/sub-folder/": "/new-folder/"
},
"changeOrigin": true,
"logLevel": "debug"
}
}
edit package.json file start script to be
"start": "ng serve --proxy-config proxy.config.json"
relaunch the npm start
A class should have only one reason to change. help to produce more loosely coupled and modular systems.
open for extension:objects and entities able to add new features or components.
closed for modification: not introducing breaking changes to existing functionality because it can influence a lot of existing code.
Subclass should override the parent class method in a way that does not break functionality.
client should never be forced to implement an interface that it doesn't use.
Entities must depend on abstraction not on concretions.
The application should avoid specifying behavior related to a particular concept in multiple places as this practice is a frequent source of errors. At some point, a change in requirements will require changing this behavior. Rather than duplicating logic, encapsulate it in a programming construct.
design principle which states that designs and/or systems should be as simple as possible.
Should be stored in Directives folder of Shared Module.
Called when Angular detects a change in the value or parameters passed.
called for every change detection for example for mouse-move
import {
ChangeDetectionStrategy,
Component,
Input,
OnInit
} from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
@Component({
selector: "lib-form",
templateUrl: "./form.component.html",
styleUrls: ["./form.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FormGeneralComponent implements OnInit {
public form = new FormGroup({
name: new FormControl("", [Validators.required]),
description: new FormControl(undefined, [Validators.required]),
status: new FormControl(1, [Validators.required])
});
get controls() {
return this.form.controls;
}
public ngOnInit() {
this.initializeFormValues();
}
// reset form
public reset() {
this.form.reset();
}
// populate form values
public initializeFormValues() {
this.form.patchValue({
name: "name",
description: "description",
status: 2
});
}
// GET form values
public onSubmit() {
this.form.getRawValue();
}
}
When using ngFor to loop over an array in templates, use it with a trackBy function which will return an unique identifier for each item.
When an array changes, Angular re-renders the whole DOM tree. But if you use trackBy, Angular will know which element has changed and will only make DOM changes for that particular element.
Before
<li *ngFor="let item of items;">{{ item }}</li>
After
// in the template
<li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li>
// in the component
trackByFn(index, item) {
return item.id; // unique id corresponding to the item
}
Before
let car = 'ludicrous car';
let myCar = `My ${car}`;
let yourCar = `Your ${car};
if (iHaveMoreThanOneCar) {
myCar = `${myCar}s`;
}
if (youHaveMoreThanOneCar) {
yourCar = `${youCar}s`;
}
After
// the value of car is not reassigned, so we can make it a const
const car = 'ludicrous car';
let myCar = `My ${car}`;
let yourCar = `Your ${car};
if (iHaveMoreThanOneCar) {
myCar = `${myCar}s`;
}
if (youHaveMoreThanOneCar) {
yourCar = `${youCar}s`;
}
Pipeable operators are tree-shakeable meaning only the code we need to execute will be included when they are imported.
This also makes it easy to identify unused operators in the files.
Before
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
iAmAnObservable
.map(value => value.item)
.take(1);
After
import { map, take } from 'rxjs/operators';
iAmAnObservable
.pipe(
map(value => value.item),
take(1)
);
Avoid subscribing to observables from components and instead subscribe to the observables from the template.
Before
// // template
<p>{{ textToDisplay }}</p>
// component
iAmAnObservable
.pipe(
map(value => value.item),
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
After
// template
<p>{{ textToDisplay$ | async }}</p>
// component
this.textToDisplay$ = iAmAnObservable
.pipe(
map(value => value.item)
);
Failing to unsubscribe from observables will lead to unwanted memory leaks as the observable stream is left open, potentially even after a component has been destroyed / the user has navigated to another page.
Before
iAmAnObservable
.pipe(
map(value => value.item)
)
.subscribe(item => this.textToDisplay = item);
After
Using takeUntil when you want to listen to the changes until another observable emits a value:
private _destroyed$ = new Subject();
public ngOnInit (): void {
iAmAnObservable
.pipe(
map(value => value.item)
// We want to listen to iAmAnObservable until the component is destroyed,
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
}
public ngOnDestroy (): void {
this._destroyed$.next();
this._destroyed$.complete();
}
When possible, try to lazy load the modules in your Angular application. Lazy loading is when you load something only when it is used. This will reduce the size of the application to be loaded and can improve the application boot time by not loading the modules that are not used.
Before
// app.routing.ts
{ path: 'not-lazy-loaded', component: NotLazyLoadedComponent }
After
// app.routing.ts
{
path: 'lazy-load',
loadChildren: 'lazy-load.module#LazyLoadModule'
}
// lazy-load.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { LazyLoadComponent } from './lazy-load.component';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{
path: '',
component: LazyLoadComponent
}
])
],
declarations: [
LazyLoadComponent
]
})
export class LazyModule {}
Avoid subscribing to one observable in the subscribe block of another observable. Instead, use appropriate chaining operators. Chaining operators run on observables from the operator before them. Some chaining operators are: withLatestFrom, combineLatest, etc.
Before
firstObservable$.pipe(
take(1)
)
.subscribe(firstValue => {
secondObservable$.pipe(
take(1)
)
.subscribe(secondValue => {
console.log(`Combined values are: ${firstValue} & ${secondValue}`);
});
});
After
firstObservable$.pipe(
withLatestFrom(secondObservable$),
first()
)
.subscribe(([firstValue, secondValue]) => {
console.log(`Combined values are: ${firstValue} & ${secondValue}`);
});
Why?
Code smell/readability/complexity: Not using RxJs to its full extent, suggests developer is not familiar with the RxJs API surface area.
Performance: If the observables are cold, it will subscribe to firstObservable, wait for it to complete, THEN start the second observable’s work. If these were network requests it would show as synchronous/waterfall.
Always declare variables or constants with a type other than any.
Extract the pieces that can be reused in a component and make it a new one. Make the component as dumb as possible, as this will make it work in more scenarios. Making a component dumb means that the component does not have any special logic in it and operates purely based on the inputs and outputs provided to it.
Reusable components reduce duplication of code therefore making it easier to maintain and make changes.
Avoid having any logic other than display logic in your component whenever you can and make the component deal only with the display logic.
Components are designed for presentational purposes and control what the view should do. Any business logic should be extracted into its own methods/services where appropriate, separating business logic from view logic.
Business logic is usually easier to unit test when extracted out to a service, and can be reused by any other components that need the same business logic applied.
Long methods generally indicate that they are doing too many things. Try to use the Single Responsibility Principle.
Long methods are hard to read, understand and maintain. They are also prone to bugs, as changing one thing might affect a lot of other things in that method.
When making API calls, responses from some of them do not change often. In those cases, you can add a caching mechanism and store the value from the API. When another request to the same API is made, check if there is a value for it in the cache and if so, use it. Otherwise, make the API call and cache the result.
Having a caching mechanism means avoiding unwanted API calls. By only making the API calls when required and avoiding duplication, the speed of the application improves as we do not have to wait for the network. It also means we do not download the same information over and over again.
If you have any sort of logic in your templates, even if it is a simple && clause, it is good to extract it out into its component.
Having logic in the template means that it is not possible to unit test it and therefore it is more prone to bugs when changing template code.
Before
// template
<p *ngIf="role==='developer'"> Status: Developer </p>
// component
public ngOnInit (): void {
this.role = 'developer';
}
After
// template
<p *ngIf="showDeveloperStatus"> Status: Developer </p>
// component
public ngOnInit (): void {
this.role = 'developer';
this.showDeveloperStatus = true;
}
If you have a variable of type string that can have only a set of values, instead of declaring it as a string type, you can declare the list of possible values as the type.
By declaring the type of the variable appropriately, we can avoid bugs while writing the code during compile time rather than during runtime.
Before
private myStringValue: string;
if (itShouldHaveFirstValue) {
myStringValue = 'First';
} else {
myStringValue = 'Second'
}
After
private myStringValue: 'First' | 'Second';
if (itShouldHaveFirstValue) {
myStringValue = 'First';
} else {
myStringValue = 'Other'
}
// This will give the below error
Type '"Other"' is not assignable to type '"First" | "Second"'
(property) AppComponent.myValue: "First" | "Second"
This avoids functions like this:
addOrUpdateData(data: Data, status: boolean) {
if (status) {
return this.http.post<Data>(url, data)
.pipe(this.catchHttpErrors());
}
return this.http.put<Data>(`${url}/${data.id}`, data)
.pipe(this.catchHttpErrors());
}
}
And hopefully more like this:
addData(data: Data) {
return this.http.post<Data>(url, data)
.pipe(this.catchHttpErrors());
}
updateData(data: Data) {
return this.http.put<Data>(`${url}/${data.id}`, data)
.pipe(this.catchHttpErrors());
}
It is extremely valuable to know if a piece of code is being used or not. If you let unused code stay, then in the future, it can be hard or almost impossible to be certain if it’s actually used or not.
You should create something like a “common frame” for your application where you will include common components. This comes in handy when you don’t want to contaminate your components with the same code. Since Angular doesn’t allow us to import component directly between different modules, we need to put these components inside the shared module.
// src/app/shared/components/reusable/resuable.component
export class ReusableComponent implements OnInit {
@Input() title: string;
@Input() body: string;
@Output() buttonClick = new EventEmitter();
constructor() { }
ngOnInit() {}
onButtonClick(){
this.buttonClick.emit('Button was clicked');
}
}
--------------------------------------------------------------------
// You can then use this component directly inside the components of
// your choice
// src/app/some/some.component
@Component({
selector: 'app-some',
template: `<app-reusable [title]="'Awesome title!'"
[body]="'Lorem ipsum'"
(buttonClick)="buttonClick($event)>
</app-reusable>`,
})
export class SomeComponent implements OnInit {
constructor() {}
ngOnInit() {}
public buttonClick(e){
console.log(e);
}
}
Quick tip! We’re able to control the HTML contents from the “parent” component by using the ng-content tag. Read more about content projection with ng-content.
how to create reusable components in Angular. I’ll be making use of Angular’s @Input and @Output directives?
@Input decorator allows parent component to bind it's properties to child component and thus gives the child component access to its data. These bindings are actually a reference to properties on the parent component.
Say we have two components CourseListComponent to the CourseItemComponent and we want to pass "course" information from our CourseListComponent to the CourseItemComponent.
we have our course model
export class Course {
public name: string;
public description: string;
public courseImagePath: string;
constructor(name: string, desc: string, courseImagePath: string) {
this.name = name;
this.description = desc;
this.courseImagePath = imagePath;
}
}
our course-list component looks something like this:
...
export class CourseListComponent implements OnInit {
courses: Course[] = [
new Course('Angular Course 1', 'This is simply a practice Angular course 1', 'http://via.digital.com/350x150.jpeg'),
new Course('Angular Course 2', 'This is simply a practice Angular course 2', 'http://via.digital.com/350x150'),
new Course('Angular Course 3', 'This is simply a practice Angular course 3', 'http://via.digital.com/350x150')
];
constructor() { }
ngOnInit() {
}
}
Below is how we would update our course-list template:
<div class="row">
<div class="col-xs-12">
<app-course-item
*ngFor="let courseEl of courses"
[course]="courseEl"></app-course-item>
</div>
</div>
We pass the individual course element for that iteration to the CourseItemComponent. Thus, we are binding the "course" property of the child CourseItemComponent from the parent CourseListComponent.
Now, to be able to do this, we have to use the @Input decorator on the child CourseItemComponent. Thus, our updated CourseItemComponent would look like below:
export class CourseItemComponent implements OnInit {
@Input() course: Course;
constructor() { }
ngOnInit() { }
}
We can now render the course name/description/image in the CourseItemComponent template as below:
<a
href="#"
class="list-group-item clearfix">
<div class="pull-left">
<h4 class="list-group-item-heading">{{ course.name }}</h4>
<p class="list-group-item-text">{{ course.description }}</p>
</div>
<span class="pull-right">
<img
[src]="course.courseImagePath"
alt="{{ course.name }}"
class="img-responsive"
style="max-height: 50px;">
</span>
</a>
In certain cases, we want to be able to emit data back from the child component to the parent component. There is an EventEmitter property exposed by the Child component exposes, which would emit data whenever any action/event occurs on the child component.
So, we'll have a "click" event on the CourseItemComponent. This is how the template for that would be updated to:
<a
href="#"
class="list-group-item clearfix"
(click)="onSelected()">
<div class="pull-left">
<h4 class="list-group-item-heading">{{ course.name }}</h4>
<p class="list-group-item-text">{{ course.description }}</p>
</div>
<span class="pull-right">
<img
[src]="course.courseImagePath"
alt="{{ course.name }}"
class="img-responsive"
style="max-height: 50px;">
</span>
</a>
Now, from our CourseItemComponent typescript code, we want to be able to emit this event and inform the parent component about the event. If we want "courseSelected" to be listenable from outside, we have to use the @Output decorator. This would give access to emit() method on this property.
export class CourseItemComponent implements OnInit {
@Input() course: Course;
@Output() courseSelected = new EventEmitter<Course>();
constructor() { }
ngOnInit() { }
onSelected() {
this.courseSelected.emit(this.course);
}
}
We would now be able to listen to the "courseSelected" event on the parent CourseListComponent as shown below.
<div class="row">
<div class="col-xs-12">
<button class="btn btn-success">New Course</button>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12">
<app-course-item
*ngFor="let courseEl of courses"
[course]="courseEl"
(courseSelected)="onCourseSelected(courseEl)"></app-course-item>
</div>
</div>
Also, we'll now get access to the "course" in our CourseListComponent typescript code.
export class CourseListComponent implements OnInit {
courses: Course[] = [
new Course('Angular Course 1', 'This is simply a practice Angular course 1', 'http://via.digital.com/350x150.jpeg'),
new Course('Angular Course 2', 'This is simply a practice Angular course 2', 'http://via.digital.com/350x150')
];
constructor() { }
ngOnInit() {
}
onCourseSelected(course: Course) {
// console.log("Selected course : " + course);
}
}
If we want to create a contract for our class we should always use interfaces. By using them we can force the class to implement functions and properties declared inside the interface. Using interfaces is a perfect way of describing our object literals. If our object is of an interface type, it is obligated to implement all of the interface’s properties. We shouldn’t name our interfaces with the starting capital I letter as we do in some programming languages.
export interface User {
name: string;
age: number;
address: string;
}
We can specify optional properties, by using the question mark (?) inside an interface as well. We don’t need to populate those properties inside an object:
Objects and arrays are the reference types in javascript. If we want to copy them into another object or an array and to modify them, the best practice is to do that in an immutable way. By modifying reference types immutably, we are preserving the original objects and arrays and modifying only their copies. The easiest way to modify objects and arrays immutably is by using the es6 spread operator (…) :
this.user = {
name: 'Dzon',
age: 25,
address: 'Sunny street 34'
}
let updatedUser = {
...this.user,
name: 'Peter'
}
We are deep copying the user object and then just overriding the name property.
public results = [10, 12, 14];
let newNumbers = [...this.numbers, 45, 56];
To be on the safe side we should always use the safe navigation operator while accessing a property from an object in a component’s template. If the object is null and we try to access a property, we are going to get an exception. But if we use the save navigation (?) operator, the template will ignore the null value and will access the property once the object is not the null anymore.
<div class="col-md-3">
{{user?.name}}
</div>
- Reusable Components and Decorators Creating reusable components is one of the best techniques we can use while developing our project. We can reuse those types of components inside any parent component and pass the data through the @Input decorator. Those components can emit events by using the @Output decorator and EventEmmiter class. Here is an example of the @Input and @Output decorators in action:
export class SuccessModalComponent implements OnInit {
@Input() public modalHeaderText: string;
@Input() public modalBodyText: string;
@Input() public okButtonText: string;
@Output() public redirectOnOK = new EventEmitter();
constructor() { }
ngOnInit() {
}
public emmitEvent(){
this.redirectOnOK.emit();
}
- Using Directives Whenever we have a situation where multiple HTML elements have the same behavior (for example: when we hover over the element it receives the blue color), we should consider using attribute directives. We shouldn’t repeat the hover logic every time we need it on some HTML element. A much better way would be to create a directive and then just reuse it on the particular element.
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHover]'
})
export class HoverDirective {
@Input() public hoverColor: string;
constructor(private element: ElementRef){}
@HostListener('mouseenter') onMouseEnter(){
this.highlightElement(this.hoverColor);
}
@HostListener('mouseleave') onMouseLeave(){
this.highlightElement(null);
}
private highlightElement(color: string){
this.element.nativeElement.style.backgroundColor = color;
}
}
We need to register this directive in a required module and to call it in HTML template file:
<p appHover [hoverColor]="'blue'">this is hoverable text</p>
Lifecycle hooks play a very important part of Angular development. We should use them whenever we have an opportunity to. For example, if we need to fetch some data from a database as soon as our component is instantiated, we should use ngOnInit() lifecycle hook and not the constructor.
If we have some logic inside child components and we want to execute that logic as soon as decorator parameters are modified, we can use ngOnChanges() lifecycle hook.
If we need to clean up some resources as soon as our component is destroyed, we should use ngOnDestroy() lifecycle hook.
Even though we don’t have to implement interfaces to use the lifecycle hooks, we are better off implementing them.
Path aliases simplify paths by giving a link to the path rather than using the the full relative path.
// before:
import { SharedModule } from '../../shared/shared.module';
import { DatePipe } from '../../../date.pipe';
import { environment } from '../../../environments/environment';
// after:
import { SharedModule } from '@shared/shared.module';
import { DatePipe } from '@shared/pipes/date.pipe';
import { environment } from '@env/environment';
We add some change to tsconfig.json
{
"compilerOptions": {
...,
"baseUrl": "./",
"paths": {
"@app/*": [
"src/app/*"
],
"@core/*": [
"src/app/core/*"
],
"@shared/*": [
"src/app/shared/*"
],
"@env/*": [
"src/environments/*"
]
}
}
}
Most applications need to communicate with a remote server over the HTTP protocol, in order to perform the basic CRUD operations. With Angular, you can use HTTPClient service to achieve this communication easily. As an example, if you need to manage the Posts of your blog, you may have the following service to handle all the operations on the Post resource:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { environment } from '../../environments/environment';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class PostService {
private readonly APIUrl = environment.APIUrl + 'posts';
constructor(private httpClient: HttpClient ) { }
getList(page: number, count: number): Observable<Post[]> {
let params = new HttpParams()
.set('page', page.toString())
.set('count', count.toString());
return this.httpClient.get<Post[]>(`/${this.APIUrl}}?${params.toString()}`)
.pipe(
catchError(this.handleError)
);
}
get(id: string | number): Observable<Post> {
return this.httpClient.get<Post>(`/${this.APIUrl}}/${id}`)
.pipe(
catchError(this.handleError)
);
}
add(post: Post): Observable<any> {
return this.httpClient.post(`/${this.APIUrl}}`, post)
.pipe(
catchError(this.handleError)
);
}
delete(id: string | number): Observable<any> {
return this.httpClient.delete(`/${this.APIUrl}}/${id}`)
.pipe(
catchError(this.handleError)
);
}
update(post: Post) {
return this.httpClient.put(`/${this.APIUrl}}`,post)
.pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
// Handle the HTTP error here
return throwError('Something wrong happened');
}
}
This solution is simple and clean, and it even follows the best practices according to the official Angular Documentation. However, applications usually have many resources to manage, for our example, we may have users, comments, reviews, etc. Ideally, each of these resources should have a separate service to handle CRUD operations and communicate with the server, at the end we will have UserService, CommentService, ReviewService.Let’s take a look at how the
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { environment } from '../../environments/environment';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class CommentService {
private readonly APIUrl = environment.APIUrl + 'comments';
constructor(private httpClient: HttpClient ) { }
getList(index: number, page: number): Observable<Comment[]> {
let params = new HttpParams()
.set('limit', index.toString())
.set('offset', page.toString());
return this.httpClient.get<Comment[]>(`/${this.APIUrl}}?${params.toString()}`)
.pipe(
catchError(this.handleError)
);
}
get(id: string | number): Observable<Comment> {
return this.httpClient.get<Comment>(`/${this.APIUrl}}/${id}`)
.pipe(
catchError(this.handleError)
);
}
add(comment: Comment): Observable<any> {
return this.httpClient.post(`/${this.APIUrl}}`, comment)
.pipe(
catchError(this.handleError)
);
}
delete(id: string | number): Observable<any> {
return this.httpClient.delete(`/${this.APIUrl}}/${id}`)
.pipe(
catchError(this.handleError)
);
}
update(comment: Comment) {
return this.httpClient.put(`/${this.APIUrl}}`,comment)
.pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
// Handle the HTTP error here
return throwError('Something wrong happened');
}
}
- The Problem Although the above implementation is very common and widly acceptable, it sufferes from two cons:
- Code redundant (breaking of the DRY principle): If you compare the PostService and the CommentService you will notice how redundant the code is.
- Changes in the server-side, or changes in the way to communicate to the server, require changes in many files (in our case we need to change both PostService and CommentService files)
- Typescript Generics To The Rescue To solve the above issues let’s go ahead and build the following abstract class which will be the base of all the other services:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { environment } from '../../environments/environment';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export abstract class ResourceService<T> {
private readonly APIUrl = environment.APIUrl + this.getResourceUrl();
constructor(protected httpClient: HttpClient ) {
}
abstract getResourceUrl(): string;
getList(page: number, count: number): Observable<T[]> {
let params = new HttpParams()
.set('page', page.toString())
.set('count', count.toString());
return this.httpClient.get<T[]>(`/${this.APIUrl}}?${params.toString()}`)
.pipe(
catchError(this.handleError)
);
}
get(id: string | number): Observable<T> {
return this.httpClient.get<T>(`/${this.APIUrl}}/${id}`)
.pipe(
catchError(this.handleError)
);
}
add(resource: T): Observable<any> {
return this.httpClient.post(`/${this.APIUrl}}`, resource)
.pipe(
catchError(this.handleError)
);
}
delete(id: string | number): Observable<any> {
return this.httpClient.delete(`/${this.APIUrl}}/${id}`)
.pipe(
catchError(this.handleError)
);
}
update(resource: T) {
return this.httpClient.put(`/${this.APIUrl}}`,resource)
.pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
// Handle the HTTP error here
return throwError('Something wrong happened');
}
}
- The new service class is abstract, which means it can’t be instantiated and used directly, but it needs to be extended by other classes.
- We provide one abstract method getResourceUrl, The class which extends this abstract class must implement this method, and return the URL of the resource as we will see in the following section.
- This is a Generic Class, it is not tied to a specific type, rather the class which extends this abstract class will define the exact type used.
- It has all the needed CRUD operations which we need and used before in the previous service.
Now after we have our abstract generic class, whenever we need a new service we can simply extend this class and implement the only one abstract method getResourceUrl.
- Server vs Front-end Model
In most applications, the front-end model doesn’t match %100 the server-side model. In other words, the REST API will respond with json object that doesn’t match exactly the interface or the class defined in the front-end application. In this case, you need a mapping function to convert between server and front-side mode. This sometimes referred to as serializing/deserializing.
So, let us extend our base class to provide this mapping functionality. To do so I updated the ResourceService to look as the following:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { environment } from '../../environments/environment';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export abstract class ResourceService<T> {
private readonly APIUrl = environment.APIUrl + this.getResourceUrl();
constructor(protected httpClient: HttpClient ) {
}
abstract getResourceUrl(): string;
toServerModel(entity: T): any {
return entity;
}
fromServerModel(json: any): T {
return json;
}
getList(index: number, page: number): Observable<T[]> {
let params = new HttpParams()
.set('limit', index.toString())
.set('offset', page.toString());
return this.httpClient.get<T[]>(`/${this.APIUrl}?${params.toString()}`)
.pipe(
map((list) => list.map((item)=> this.fromServerModel(item))),
catchError(this.handleError)
);
}
get(id: string | number): Observable<T> {
return this.httpClient.get<T>(`/${this.APIUrl}/${id}`)
.pipe(
map((json) => this.fromServerModel(json)),
catchError(this.handleError)
);
}
add(resource: T): Observable<any> {
return this.httpClient.post(`/${this.APIUrl}`, this.toServerModel(resource))
.pipe(
catchError(this.handleError)
);
}
delete(id: string | number): Observable<any> {
return this.httpClient.delete(`/${this.APIUrl}/${id}`)
.pipe(
catchError(this.handleError)
);
}
update(resource: T) {
return this.httpClient.put(`/${this.APIUrl}`, this.toServerModel(resource))
.pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
// Handle the HTTP error here
return throwError('Something wrong happened');
}
}
Now to consume the new changes we have two cases:
- There is no mapping needed between the server and the front-end model, in this case, we don’t have to change anything in the class that extends the resourceService.
- There is some kind of mapping needed between the server and the front-end model, all we need to do is to override toServerModel and fromServerModel models in the derived class to address our requirement mappings. For example let’s assume, that the PostsService implemented previously needs to map from timestamp to a js Date object, the PostsService implementation would look like the following:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ResourceService } from '../resource/resource.service';
@Injectable({
providedIn: 'root'
})
export class PostsService extends ResourceService<Post>{
constructor(protected httpClient: HttpClient ) {
super(httpClient);
}
getResourceUrl(): string {
return 'posts';
}
toServerModel(entity: Post): any {
return {
...entity,
createDate: entity.createDate.getTime()
};
}
fromServerModel(json: any): Post{
return {
...json,
createDate: new Date(json.createDate)
};
}
}
Then create a new file called Dockerfile that will be located in the project’s root folder. It should have these following lines:
### STAGE 1: Build ###
FROM node:12.20-alpine3.10 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build:prod
### STAGE 2: Run ###
FROM nginx:1.17.1-alpine
COPY --from=build /app/dist/ngx-levi9 /usr/share/nginx/html
FROM node:12.20-alpine3.10 As build
This line will tell the docker to pull the node image with tag 12.20-alpine3.10 if the images don't exist. We are also giving a friendly name build to this image so we can refer it later.
WORKDIR /app
This WORKDIR command will create the working directory in our docker image. going forward any command will be run in the context of this directory.
COPY package.json package-lock.json ./
This COPY command will copy package.json and package-lock.json from our current directory to the root of our working directory inside a container which is /app.
RUN npm install
This RUN command will restore node_modules define in our package.json
COPY . .
This COPY command copies all the files from our current directory to the container working directory. this will copy all our source files.
RUN npm run build:prod
This command will build our angular project in production mode and create production ready files in dist/appName folder
FROM nginx:1.17.1-alpine
This line will create a second stage nginx container where we will copy the compiled output from our build stage
COPY --from=build /app/dist/ngx-levi9 /usr/share/nginx/html
This is the final command of our docker file. This will copy the compiled angular app from builder stage path /app/dist/ngx-levi9 to nginx container
When COPY . . command execute it will copy all the files in the host directory to the container working directory. if we want to ignore some folder like .git or node_modules we can define these folders in .dockerignore file.
.git
node_modules
Navigate the project folder in command prompt and run the below command to build the image
docker build . -t dockerngstart .
This command will look for a docker file in the current directory and create the image with tag dockerngstart. with -t command you can specify the tag for the image the default convention is DockerHubUsername/ImageName.
You can run the docker image using the below command
docker run --name dockerngstart-container -it -p 8000:80 dockerngstart
A container may be running, but you may not be able to interact with it. To start the container in interactive mode, use the –i and –t options
Navigate to your browser with http://localhost:8000
Run docker images command to list all the docker images in your machine
docker images
Remove docker image
docker rmi <IMAGE ID>
To list all running Docker containers, enter the following into a terminal window
docker ps
To list all containers, both running and stopped, add –a
docker ps -a
The main command to launch or start a single or multiple stopped Docker containers is docker start:
docker start [options] container_id
To create a new container from an image and start it, use docker run
docker run [options] image [command] [argument]
By default, you get a 10 second grace period. The stop command instructs the container to stop services after that period. Use the --time option to define a different grace period expressed in seconds
docker stop [option] container_id
docker stop --time=20 container_id
To immediately kill a docker container without waiting for the grace period to end use:
docker kill [option] container_id
To stop all running containers, enter the following:
docker stop $(docker ps –a –q)
These template elements only work in the presence of structural directives, which help us to define a template that doesn’t render anything by itself, but conditionally renders them to the DOM. It helps us create dynamic templates that can be customized and configured.
<div>
Ng-template Content
<div *ngIf=”false else showNgTemplateContent”>
Shouldn't be displayed
</div>
</div>
<ng-template #showNgTemplateContent> Should be displayed
</ng-template>
ng-container is an extremely simple directive that allows you to group elements in a template that doesn’t interfere with styles or layout because Angular doesn’t put it in the DOM This is helpful if you don’t want any extra div on DOM, you can simply use ng-container. For eg: If there are two structural directives are being called on one div as below:
<div *ngIf="details" *ngFor="let info of details">
{{ info.content }}
</div>
Attempting to compile this code will result in the following error:
Can't have multiple template bindings on one element. Use only one attribute prefixed with *
One workaround would be to separate the bindings as below:
<div *ngIf="details">
<div *ngFor="let info of details">
{{ info.content }}
</div>
</div>
Or we can use without adding any extra element to the DOM at runtime:
<ng-container *ngIf="details">
<div *ngFor="let info of details">
{{ info.content }}
</div>
</ng-container>
ng-content is used to project content into Angular components. You use the tag as a placeholder for that dynamic content, then when the template is parsed Angular will replace that placeholder tag with your content.
src/app/my-card/my-card.component.html
<div class="card">
<div class="card-header">
<ng-content select=".header"></ng-content>
</div>
<div class="card-body">
<ng-content select=".content"></ng-content>
</div>
<div class="card-footer">
<ng-content select=".footer"></ng-content>
</div>
</div>
Here, we will use the dynamic data and call our component like this:
src/app/app.component.html
<my-card>
<ng-container class="header">
Example of ng-container Select by Class using Angular
</ng-container>
<ng-container class="content">
Here, we will learn about ng-content using angular. We will also learn the use of ng-content in angular. This example is used to display ng-content select by class using Angular.
</ng-container>
<ng-container class="footer">
This example is from Javatpoint.com
</ng-container>
</my-card>