Debugging Angular CLI Applications in Visual Studio Code

Debugging Angular CLI Applications in Visual Studio Code

In this post, we are going to create an Angular CLI application, then add configuration to debug it in Visual Studio Code.


<span class="ql-cursor"></span>For an Angular CLI application, create a debug configuration in VS Code, install the Debugger for Chrome extension, then run in debug mode.

Learn VS Code

If you’re interested in learning more about VS Code, you definitely want to check out the upcoming Learn VS Code course.

Creating a Starter Project

To be able to test an Angular CLI application, you need an Angular CLI application :) I’ll provide the basic steps, but for more reference on how to get started look at the Angular CLI page.

First, you’ll need to install the Angular CLI.

npm install -g @angular/cli

After that finishes, you’ll need to actually generate your new application. This will take a bit as it needs to install LOTS of NPM packages.

ng new my-app

Open the project in VS Code and you should see the following.

Now, that you’ve got your new fancy Angular app, go ahead and run it to make sure everything looks right.

ng serve

Should look like this.


Creating Debug Configuration

Assuming you’ve made it this far, we are ready to start debugging! Before we do, however, it’s worth understanding how configuring debugging in VS Code works. Basically debug configurations are saved in a launch.json file which is stored inside of a .vscode folder. This .vscode folder is used to store different configurations for Code including our required debugging stuff.

Before you create your debug configuration, you need to install the Debugger for Chrome extension. Find and install this extension from the extension tab in VS Code. After installing, reload VS Code.

Now, to create a debug configuration, you can open the debug panel (the bug looking button on the left panel). At the top of the debug panel, you should see a dropdown that says “No Configurations”.

To the right of that dropdown, there is a gear icon. Click this button to have VS Code automatically generate that .vscode folder and launch.json file mentioned above.

Then choose Chrome.

You should get the following configuration created for you.

The only thing we need to do is update the port from 8080 to 4200.


Let’s Debug

Now we’re ready! Go ahead and click the play button at the top of the Debug panel which will launch an instance of Chrome in debug mode. Keep in mind your app should already be running from using ng serve earlier. In VS Code, you should see the Debug toolbar pop up.

With this up and running, you can set a breakpoint in your App.component.ts. Open up your App.component.ts and add a breakpoint inside of the component by clicking in the gutter (to the left of the line numbers). Should look like this.

Now, refresh debugging by clicking the refresh button on the debugging toolbar. This should open your application again and trigger this breakpoin. You should be directed back to VS Code directly to the place where you set your breakpoint.

From here, you can set more breakpoints, inspect variables, etc. If you are interested in learning more about debugging JavaScript in general in either Chrome or VS Code you can check out Debugging JavaScript in Chrome and Visual Studio Code.


Learn more

Angular 7 Tutorial - Learn Angular 7 by Example

Build a Simple Web App with Express, Angular, and GraphQL

Build a Basic CRUD App with Laravel and Angular

AngularJS tutorial for beginners with NodeJS, ExpressJS and MongoDB

MEAN Stack Tutorial MongoDB, ExpressJS, AngularJS and NodeJS

Angular Authentication Tutorial

Angular 7 + Spring Boot CRUD Example

Angular 7 + Spring Boot Login Authentication Example

Angular 7 (formerly Angular 2) - The Complete Guide

Angular & NodeJS - The MEAN Stack Guide

Learn and Understand AngularJS

Angular Crash Course for Busy Developers

Originally published by James Quick at https://scotch.io

Angular Architecture Patterns and Best Practices (that help to scale)

Angular Architecture Patterns and Best Practices (that help to scale)
<p class="ql-align-center">Originally published by Bartosz Pietrucha at angular-academy.com</p><p> In order to deal with mentioned factors to maintain a high quality of delivery and prevent technical debt, robust and well-grounded architecture is necessary. Angular itself is a quite opinionated framework, forcing developers to do things the proper way, yet there are a lot of places where things can go wrong. In this article, I will present high-level recommendations of well-designed Angular application architecture based on best practices and battle-proven patterns. Our ultimate goal in this article is to learn how to design Angular application in order to maintain sustainable development speed and ease of adding new features in the long run. To achieve these goals, we will apply:</p>
    <li>proper abstractions between application layers,</li><li>unidirectional data flow,</li><li>reactive state management,</li><li>modular design,</li><li>smart and dumb components pattern.</li>
<p></p>

Problems of scalability in front-end

<p>Let's think about problems in terms of scalability we can face in the development of modern front-end applications. Today, front-end applications are not "just displaying" data and accepting user inputs. Single Page Applications (SPAs) are providing users with rich interactions and use backend mostly as a data persistence layer. This means, far more responsibility has been moved to the front-end part of software systems. This leads to a growing complexity of front-end logic, we need to deal with. Not only the number of requirements grows over time, but the amount of data we load into the application is increasing. On top of that, we need to maintain application performance, which can easily be hurt. Finally, our development teams are growing (or at least rotating - people come and go) and it is important for new-comers to get up to speed as fast as possible.</p><p></p><p>One of the solutions to the problems described above is solid system architecture. But, this comes with the cost, the cost of investing in that architecture from day one. It can be very tempting for us developers, to deliver new features very quickly, when the system is still very small. At this stage, everything is easy and understandable, so development goes really fast. But, unless we care about the architecture, after a few developers rotations, tricky features, refactorings, a couple of new modules, the speed of development slows down radically. Below diagram presents how it usually looked like in my development career. This is not any scientifical study, it's just how I see it.</p><p></p>

Software architecture

<p>To discuss architecture best practices and patterns, we need to answer a question, what the software architecture is, in the first place. Martin Fowlerdefines architecture as "highest-level breakdown of a system into its parts". On top of that, I would say that software architecture describes how the software is composed of its parts and what are the rules and constraints of the communication between those parts. Usually, the architectural decisions that we make in our system development, are hard to change as the system grows over time. That's why it is very important to pay attention to those decisions from the very beginning of our project, especially if the software we build is supposed to be running in production for many years. Robert C. Martin once said: the true cost of software is its maintenance. Having well-grounded architecture helps to reduce the costs of the system's maintenance.</p>
Software architecture is the way the software is composed of its parts and the rules and constraints of the communication between those parts

High-level abstraction layers

<p>The first way, we will be decomposing our system, is through the abstraction layers. Below diagram depicts the general concept of this decomposition. The idea is to place the proper responsibility into the proper layer of the system: coreabstraction or presentation layer. We will be looking at each layer independently and analyzing its responsibility. This division of the system also dictates communication rules. For example, the presentation layer can talk to the core layer only through the abstractionlayer. Later, we will learn what are the benefits of this kind of constraint.</p><p></p>

Presentation layer

<p>Let's start analyzing our system break-down from the presentation layer. This is the place where all our Angular components live. The only responsibilities of this layer are to present and to delegate. In other words, it presents the UI and delegates user's actions to the core layer, through the abstraction layer. It knows what to display and what to do, but it does not know how the user's interactions should be handled.</p><p>Below code snippet contains CategoriesComponent using SettingsFacadeinstance from abstraction layer to delegate user's interaction (via addCategory() and updateCategory()) and present some state in its template (via isUpdating$).</p><pre class="ql-syntax" spellcheck="false">@Component({ selector: 'categories', templateUrl: './categories.component.html', styleUrls: ['./categories.component.scss'] }) export class CategoriesComponent implements OnInit {

@Input() cashflowCategories$: CashflowCategory[];
newCategory: CashflowCategory = new CashflowCategory();
isUpdating$: Observable<boolean>;

constructor(private settingsFacade: SettingsFacade) {
this.isUpdating$ = settingsFacade.isUpdating$();
}

ngOnInit() {
this.settingsFacade.loadCashflowCategories();
}

addCategory(category: CashflowCategory) {
this.settingsFacade.addCashflowCategory(category);
}

updateCategory(category: CashflowCategory) {
this.settingsFacade.updateCashflowCategory(category);
}

}
</pre>

Abstraction layer

<p>The abstraction layer decouples the presentation layer from the core layer and also has it's very own defined responsibilities. This layer exposes the streams of state and interface for the components in the presentation layer, playing the role of the facade. This kind of facade sandboxes what components can see and do in the system. We can implement facades by simply using Angular class providers. The classes here may be named with Facade postfix, for example SettingsFacade. Below, you can find an example of such a facade.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class SettingsFacade {

constructor(private cashflowCategoryApi: CashflowCategoryApi, private settingsState: SettingsState) { }

isUpdating$(): Observable<boolean> {
return this.settingsState.isUpdating$();
}

getCashflowCategories$(): Observable<CashflowCategory[]> {
// here we just pass the state without any projections
// it may happen that it is necessary to combine two or more streams and expose to the components
return this.settingsState.getCashflowCategories$();
}

loadCashflowCategories() {
return this.cashflowCategoryApi.getCashflowCategories()
.pipe(tap(categories => this.settingsState.setCashflowCategories(categories)));
}

// optimistic update
// 1. update UI state
// 2. call API
addCashflowCategory(category: CashflowCategory) {
this.settingsState.addCashflowCategory(category);
this.cashflowCategoryApi.createCashflowCategory(category)
.subscribe(
(addedCategoryWithId: CashflowCategory) => {
// success callback - we have id generated by the server, let's update the state
this.settingsState.updateCashflowCategoryId(category, addedCategoryWithId)
},
(error: any) => {
// error callback - we need to rollback the state change
this.settingsState.removeCashflowCategory(category);
console.log(error);
}
);
}

// pessimistic update
// 1. call API
// 2. update UI state
updateCashflowCategory(category: CashflowCategory) {
this.settingsState.setUpdating(true);
this.cashflowCategoryApi.updateCashflowCategory(category)
.subscribe(
() => this.settingsState.updateCashflowCategory(category),
(error) => console.log(error),
() => this.settingsState.setUpdating(false)
);
}
}
</pre>

Abstraction interface

<p>We already know the main responsibilities for this layer; to expose streams of state and interface for the components. Let's start with the interface. Public methods loadCashflowCategories()addCashflowCategory() and updateCashflowCategory() abstract away the details of state management and the external API calls from the components. We are not using API providers (like CashflowCategoryApi) in components directly, as they live in the core layer. Also, how the state changes is not a concern of the components. The presentation layer should not care about how things are done and components should just call the methods from the abstraction layer when necessary (delegate). Looking at the public methods in our abstraction layer should give us a quick insight about high-level use casesin this part of the system.</p><p>But we should remember that the abstraction layer is not a place to implement business logic. Here we just want to connect the presentation layer to our business logic, abstracting the way it is connected.</p>

State

<p>When it comes to the state, the abstraction layer makes our components independent of the state management solution. Components are given Observables with data to display on the templates (usually with async pipe) and don't care how and where this data comes from. To manage our state we can pick any state management library that supports RxJS (like NgRx) or simple use BehaviorSubjects to model our state. In the example above we are using state object that internally uses BehaviorSubjects (state object is a part of our core layer). In the case of NgRx, we would dispatch actions for the store.</p><p>Having this kind abstraction gives us a lot of flexibility and allows to change the way we manage state not even touching the presentation layer. It's even possible to seamlessly migrate to a real-time backend like Firebase, making our application real-time. I personally like to start with BehaviorSubjects to manage the state. If later, at some point in the development of the system, there is a need to use something else, with this kind of architecture, it is very easy to refactor.</p>

Synchronization strategy

<p>Now, let's take a closer look at the other important aspect of the abstraction layer. Regardless of the state management solution we choose, we can implement UI updates in either optimistic or pessimistic fashion. Imagine we want to create a new record in the collection of some entities. This collection was fetched from the backend and displayed in the DOM. In a pessimistic approach, we first try to update the state on the backend side (for example with HTTP request) and in case of success we update the state in the frontend application. On the other hand, in an optimistic approach, we do it in a different order. First, we assume that the backend update will succeed and update frontend state immediately. Then we send request to update server state. In case of success, we don't need to do anything, but in case of failure, we need to rollback the change in our frontend application and inform the user about this situation.</p>
Optimistic update changes the UI state first and attempts to update the backend state. This provides a user with a better experience, as he does not see any delays, because of network latency. If backend update fails, then UI change has to be rolled back.
Pessimistic update changes the backend state first and only in case of success updates the UI state. Usually, it is necessary to show some kind of spinner or loading bar during the execution of backend request, because of network latency.

Caching

<p>Sometimes, we may decide that the data we fetch from the backend will not be a part of our application state. This may be useful for read-only data that we don't want to manipulate at all and just pass (via abstraction layer) to the components. In this case, we can apply data caching in our facade. The easiest way to achieve it is to use shareReplay() RxJS operator that will replay the last value in the stream for each new subscriber. Take a look at the code snippet below with RecordsFacade using RecordsApi to fetch, cache and filter the data for the components.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class RecordsFacade {

private records$: Observable<Record[]>;

constructor(private recordApi: RecordApi) {
this.records$ = this.recordApi
.getRecords()
.pipe(shareReplay(1)); // cache the data
}

getRecords() {
return this.records$;
}

// project the cached data for the component
getRecordsFromPeriod(period?: Period): Observable<Record[]> {
return this.records$
.pipe(map(records => records.filter(record => record.inPeriod(period))));
}

searchRecords(search: string): Observable<Record[]> {
return this.recordApi.searchRecords(search);
}
}
</pre><p>To sum up, what we can do in the abstraction layer is to:</p>

    <li>expose methods for the components in which we:</li><li class="ql-indent-1">delegate logic execution to the core layer,</li><li class="ql-indent-1">decide about data synchronization strategy (optimistic vs. pessimistic),</li><li>expose streams of state for the components:</li><li class="ql-indent-1">pick one or more streams of UI state (and combine them if necessary),</li><li class="ql-indent-1">cache data from external API.</li>
<p>As we see, the abstraction layer plays an important role in our layered architecture. It has clearly defined responsibilities what helps to better understand and reason about the system. Depending on your particular case, you can create one facade per Angular module or one per each entity. For example, the SettingsModule may have a single SettingsFacade, if it's not too bloated. But sometimes it may be better to create more-granular abstraction facades for each entity individually, like UserFacade for Userentity.</p>

Core layer

<p>The last layer is the core layer. Here is where core application logic is implemented. All data manipulation and outside world communicationhappen here. If for state management, we were using a solution like NgRx, here is a place to put our state definition, actions and reducers. Since in our examples we are modeling state with BehaviorSubjects, we can encapsulate it in a convenient state class. Below, you can find SettingsState example from the core layer.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class SettingsState {

private updating$ = new BehaviorSubject<boolean>(false);
private cashflowCategories$ = new BehaviorSubject<CashflowCategory[]>(null);

isUpdating$() {
return this.updating$.asObservable();
}

setUpdating(isUpdating: boolean) {
this.updating$.next(isUpdating);
}

getCashflowCategories$() {
return this.cashflowCategories$.asObservable();
}

setCashflowCategories(categories: CashflowCategory[]) {
this.cashflowCategories$.next(categories);
}

addCashflowCategory(category: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next([...currentValue, category]);
}

updateCashflowCategory(updatedCategory: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const indexOfUpdated = categories.findIndex(category => category.id === updatedCategory.id);
categories[indexOfUpdated] = updatedCategory;
this.cashflowCategories$.next([...categories]);
}

updateCashflowCategoryId(categoryToReplace: CashflowCategory, addedCategoryWithId: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const updatedCategoryIndex = categories.findIndex(category => category === categoryToReplace);
categories[updatedCategoryIndex] = addedCategoryWithId;
this.cashflowCategories$.next([...categories]);
}

removeCashflowCategory(categoryRemove: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next(currentValue.filter(category => category !== categoryRemove));
}
}
</pre><p>In the core layer, we also implement HTTP queries in the form of class providers. This kind of class could have Api or Service name postfix. API services have only one responsibility - it is just to communicate with API endpoints and nothing else. We should avoid any caching, logic or data manipulation here. A simple example of API service can be found below.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class CashflowCategoryApi {

readonly API = '/api/cashflowCategories';

constructor(private http: HttpClient) {}

getCashflowCategories(): Observable<CashflowCategory[]> {
return this.http.get<CashflowCategory[]>(this.API);
}

createCashflowCategory(category: CashflowCategory): Observable<any> {
return this.http.post(this.API, category);
}

updateCashflowCategory(category: CashflowCategory): Observable<any> {
return this.http.put(${this.API}/${category.id}, category);
}

}
</pre><p>In this layer, we could also place any validators, mappers or more advanced use-cases that require manipulating many slices of our UI state.</p><p>We have covered the topic of the abstraction layers in our frontend application. Every layer has it's well-defined boundaries and responsibilities. We also defined the strict rules of communication between layers. This all helps to better understand and reason about the system over time as it becomes more and more complex.</p><p></p><p>If you need help with your project, check out Angualar Academy Workshops or write an email to [email protected].</p>

Unidirectional data flow and reactive state management

<p>The next principle we want to introduce in our system is about the data flow and propagation of change. Angular itself uses unidirectional data flow on presentation level (via input bindings), but we will impose a similar restriction on the application level. Together with reactive state management (based on streams), it will give us the very important property of the system - data consistency. Below diagram presents the general idea of unidirectional data flow.</p><p></p><p>Whenever any model value change in our application, Angular change detection system takes care of the propagation of that change. It does it via input property bindings from the top to bottom of the whole component tree. It means that a child component can only depend on its parent and never vice versa. This is why we call it unidirectional data flow. This allows Angular to traverse the components tree only once (as there are no cycles in the tree structure) to achieve a stable state, which means that every value in the bindings is propagated.</p><p>As we know from previous chapters, there is the core layer above the presentation layer, where our application logic is implemented. There are the services and providers that operate on our data. What if we apply the same principle of data manipulation on that level? We can place the application data (the state) in one place "above" the components and propagate the values down to the components via Observable streams (Redux and NgRx call this place a store). The state can be propagated to multiple components and displayed in multiple places, but never modified locally. The change may come only "from above" and the components below only "reflect" the current state of the system. This gives us the important system's property mentioned before - data consistency - and the state object becomes the single source of truth. Practically speaking, we can display the same data in multiple places and not be afraid that the values would differ.</p><p>Our state object exposes the methods for the services in our core layer to manipulate the state. Whenever there is a need to change the state, it can happen only by calling a method on the state object (or dispatching an action in case of using NgRx). Then, the change is propagated "down", via streams, the to presentation layer (or any other service). This way, our state management is reactive. Moreover, with this approach, we also increase the level of predictability in our system, because of strict rules of manipulating and sharing the application state. Below you can find a code snippet modeling the state with BehaviorSubjects.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class SettingsState {

private updating$ = new BehaviorSubject<boolean>(false);
private cashflowCategories$ = new BehaviorSubject<CashflowCategory[]>(null);

isUpdating$() {
return this.updating$.asObservable();
}

setUpdating(isUpdating: boolean) {
this.updating$.next(isUpdating);
}

getCashflowCategories$() {
return this.cashflowCategories$.asObservable();
}

setCashflowCategories(categories: CashflowCategory[]) {
this.cashflowCategories$.next(categories);
}

addCashflowCategory(category: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next([...currentValue, category]);
}

updateCashflowCategory(updatedCategory: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const indexOfUpdated = categories.findIndex(category => category.id === updatedCategory.id);
categories[indexOfUpdated] = updatedCategory;
this.cashflowCategories$.next([...categories]);
}

updateCashflowCategoryId(categoryToReplace: CashflowCategory, addedCategoryWithId: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const updatedCategoryIndex = categories.findIndex(category => category === categoryToReplace);
categories[updatedCategoryIndex] = addedCategoryWithId;
this.cashflowCategories$.next([...categories]);
}

removeCashflowCategory(categoryRemove: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next(currentValue.filter(category => category !== categoryRemove));
}
}

</pre><p>Let's recap the steps of handling the user interaction, having in mind all the principles we have already introduced. First, let's imagine that there is some event in the presentation layer (for example button click). The component delegates the execution to the abstraction layer, calling the method on the facade settingsFacade.addCategory(). Then, the facade calls the methods on the services in the core layer - categoryApi.create() and settingsState.addCategory(). The order of invocation of those two methods depends on synchronization strategy we choose (pessimistic or optimistic). Finally, the application state is propagated down to the presentation layer via the observable streams. This process is well-defined.</p><p></p>

Modular design

<p>We have covered the horizontal division in our system and the communication patterns across it. Now we are going to introduce a vertical separation into feature modules. The idea is to slice the application into feature modules representing different business functionalities. This is yet another step to deconstruct the system into smaller pieces for better maintainability. Each of the features modules share the same horizontal separation of the core, abstraction, and presentation layer. It is important to note, that these modules could be lazily loaded (and preloaded) into the browser increasing the initial load time of the application. Below you can find a diagram illustrating features modules separation.</p><p></p><p>Our application has also two additional modules for more technical reasons. We have a CoreModule that defines our singleton services, single-instance components, configuration, and export any third-party modules needed in AppModule. This module is imported only once in AppModule. The second module is SharedModule that contains common components/pipes/directives and also export commonly used Angular modules (like CommonModule). SharedModule can be imported by any feature module. The diagram below presents the imports structure.</p><p></p>

Module directory structure

<p>Below diagram presents how we can place all the pieces of our SettingsModule inside the directories. We can put the files inside of the folders with a name representing their function.</p><p></p>

Smart and dumb components

<p>The final architectural pattern we introduce in this article is about components themselves. We want to divide components into two categories, depending on their responsibilities. First, are the smart components (aka containers). These components usually:</p>
    <li>have facade/s and other services injected,</li><li>communicate with the core layer,</li><li>pass data to the dumb components,</li><li>react to the events from dumb components,</li><li>are top-level routable components (but not always!).</li>
<p>Previously presented CategoriesComponent is smart. It has SettingsFacadeinjected and uses it to communicate with the core layer of our application.</p><p>In the second category, there are dumb components (aka presentational). Their only responsibilities are to present UI element and to delegate user interaction "up" to the smart components via events. Think of a native HTML element like <button>Click me</button>. That element does not have any particular logic implemented. We can think of the text 'Click me' as an input for this component. It also has some events that can be subscribed to, like click event. Below you can find a code snippet of a simple presentational component with one input and no output events.</p><pre class="ql-syntax" spellcheck="false">@Component({
selector: 'budget-progress',
templateUrl: './budget-progress.component.html',
styleUrls: ['./budget-progress.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BudgetProgressComponent {

@Input()
budget: Budget;
today: string;

}
</pre>

Summary

<p>We have covered a couple of ideas on how to design the architecture of an Angular application. These principles, if applied wisely, can help to maintain sustainable development speed over time, and allow new features to be delivered easily. Please don't treat them as some strict rules, but rather recommendations that could be employed when they make sense.</p><p>We have taken a close look at the abstractions layers, unidirectional data flow, and reactive state management, modular design, and smart/dumb components pattern. I hope that these concepts will be helpful in your projects and, as always, if you have any questions, I am more than happy to chat with you.</p><p class="ql-align-center">Originally published by Bartosz Pietrucha at angular-academy.com</p><p>==========================================</p><p>Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter</p>

Learn More

<p>☞ Angular 8 (formerly Angular 2) - The Complete Guide</p><p>☞ Complete Angular 8 from Zero to Hero | Get Hired</p><p>☞ Learn and Understand AngularJS</p><p>☞ The Complete Angular Course: Beginner to Advanced</p><p>☞ Angular Crash Course for Busy Developers</p><p>☞ Angular Essentials (Angular 2+ with TypeScript)</p><p>☞ Angular (Full App) with Angular Material, Angularfire & NgRx</p><p>☞ Angular & NodeJS - The MEAN Stack Guide</p>

Angular 7 CRUD with Nodejs and MySQL Example

Angular 7 CRUD with Nodejs and MySQL Example

<span class="ql-cursor"></span>Below are the requirements for creating the CRUD on MEAN

  • Node.js
  • Angular CLI
  • Angular 7
  • Mysql
  • IDE or Text Editor

We assume that you have already available the above tools/frameworks and you are familiar with all the above that what individually actually does.

So now we will proceed step by step to achieve the task.

1. Update Angular CLI and Create Angular 7 Application

At first, We have to update the Angular CLI to the latest version. Open the terminal then go to the project folder and then type the below command to update the Angular CLI

<pre class="ql-syntax" spellcheck="false">sudo npm install -g @angular/cli </pre>

Once the above task finishes, Next task is to create new angular application with below command. So go to your project folder and then type below command:

<pre class="ql-syntax" spellcheck="false">ng new angular7-crud </pre>

then go to the newly created folder of angular application with **cd /angular7-crud ** and type **ng serve. **Now, open the browser then go to <a href="http://localhost:4200" title="" target="_blank">http://localhost:4200</a> you should see this page.

2. Create a server with node.js express and Mysql for REST APIs

create a separate folder named server for server-side stuff, Then move inside folder and create server.js by typing touch server.js

Let’s have a look on the server.js file

<pre class="ql-syntax" spellcheck="false">let app = require('express')(), server = require('http').Server(app), bodyParser = require('body-parser') express = require('express'), cors = require('cors'), http = require('http'), path = require('path');   let articleRoute = require('./Routes/article'), util = require('./Utilities/util');   app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false }));   app.use(cors());   app.use(function(err, req, res, next) { return res.send({ "statusCode": util.statusCode.ONE, "statusMessage":util.statusMessage.SOMETHING_WENT_WRONG }); });   app.use('/article', articleRoute);   // catch 404 and forward to error handler app.use(function(req, res, next) { next(); });   /*first API to check if server is running*/ app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../server/client/dist/index.html')); })     server.listen(3000,function(){ console.log('app listening on port: 3000'); }); </pre>

In the above file we can see, at the top, there are required packages for the app. Below that body parsing, middleware and routing is done.

The next task is to create routes and create a file article.js . So creating a folder name ‘Routes’ and adding article.js within it.

Add the below code for routing in article.js inside routing folder

<pre class="ql-syntax" spellcheck="false">let express = require('express'), router = express.Router(), util = require('../Utilities/util'), articleService = require('../Services/article');   /**Api to create article */ router.post('/create-article', (req, res) => { articleService.createArticle(req.body, (data) => { res.send(data); }); });   // /**Api to update article */ router.put('/update-article', (req, res) => { articleService.updateArticle(req.body, (data) => { res.send(data); }); });   // /**Api to delete the article */ router.delete('/delete-article', (req, res) => { articleService.deleteArticle(req.query, (data) => { res.send(data); }); });   /**Api to get the list of article */ router.get('/get-article', (req, res) => { documentService.getArticle(req.query, (data) => { res.send(data); }); });   // /**API to get the article by id... */ router.get('/get-article-by-id', (req, res) => { articleService.getArticleById(req.query, (data) => { res.send(data); }); });   module.exports = router; </pre>


Now create a folder named Utilities for all config, common methods and mysql connection config.

Now I am adding config values in a file named config.js

<pre class="ql-syntax" spellcheck="false">let environment = "dev";   let serverURLs = { "dev": { "NODE_SERVER": "http://localhost", "NODE_SERVER_PORT": "3000", "MYSQL_HOST": 'localhost', "MYSQL_USER": 'root', "MYSQL_PASSWORD": 'password', 'MYSQL_DATABASE': 'demo_angular7_crud', } }   let config = { "DB_URL_MYSQL": { "host": `${serverURLs[environment].MYSQL_HOST}`, "user": `${serverURLs[environment].MYSQL_USER}`, "password": `${serverURLs[environment].MYSQL_PASSWORD}`, "database": `${serverURLs[environment].MYSQL_DATABASE}` }, "NODE_SERVER_PORT": { "port": `${serverURLs[environment].NODE_SERVER_PORT}` }, "NODE_SERVER_URL": { "url": `${serverURLs[environment].NODE_SERVER}` } };   module.exports = { config: config }; </pre>

Now configure mysql connection. So I am writing the connection with database in a separate file. So creating a file named mysqkConfig.js under Utilities folder and adding the below line of code for mysql connection:

 

<pre class="ql-syntax" spellcheck="false">var config = require("../Utilities/config").config; var mysql = require('mysql'); var connection = mysql.createConnection({ host: config.DB_URL_MYSQL.host, user: config.DB_URL_MYSQL.user, password: config.DB_URL_MYSQL.password, database: config.DB_URL_MYSQL.database, });   connection.connect(() => { require('../Models/Article').initialize(); });   let getDB = () => { return connection; }   module.exports = { getDB: getDB } </pre>

Now I am creating separate file name util.js to save common methods and common status code/message:

<pre class="ql-syntax" spellcheck="false">// Define Error Codes let statusCode = { OK: 200, FOUR_ZERO_FOUR: 404, FOUR_ZERO_THREE: 403, FOUR_ZERO_ONE: 401, FIVE_ZERO_ZERO: 500 };   // Define Error Messages let statusMessage = { SERVER_BUSY : 'Our Servers are busy. Please try again later.', DATA_UPDATED: 'Data updated successfully.', DELETE_DATA : 'Delete data successfully',   };   module.exports = { statusCode: statusCode, statusMessage: statusMessage } </pre>

Now the next part is model, So create a folder named Models and create a file Article.js and add the below code in it:

<pre class="ql-syntax" spellcheck="false">let mysqlConfig = require("../Utilities/mysqlConfig");   let initialize = () => { mysqlConfig.getDB().query("create table IF NOT EXISTS article (id INT auto_increment primary key, category VARCHAR(30), title VARCHAR(24))");   }   module.exports = { initialize: initialize } </pre>

Now create DAO folder and add a file articleDAO.js for writting the mysql queries common functions:

<pre class="ql-syntax" spellcheck="false">let dbConfig = require("../Utilities/mysqlConfig");

 
let getArticle = (criteria, callback) => {
//criteria.aricle_id ? conditions += and aricle_id = '${criteria.aricle_id}' : true;
dbConfig.getDB().query(select * from article where 1,criteria, callback);
}
 
let getArticleDetail = (criteria, callback) => {
    let conditions = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
dbConfig.getDB().query(select * from article where 1&nbsp;${conditions}, callback);
}
 
let createArticle = (dataToSet, callback) => {
console.log("insert into article set ? ", dataToSet,'pankaj')
dbConfig.getDB().query("insert into article set ? ", dataToSet, callback);
}
 
let deleteArticle = (criteria, callback) => {
let conditions = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
console.log(delete from article where 1&nbsp;${conditions});
dbConfig.getDB().query(delete from article where 1&nbsp;${conditions}, callback);
 
}
 
let updateArticle = (criteria,dataToSet,callback) => {
    let conditions = "";
let setData = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
dataToSet.category ? setData += category = '${dataToSet.category}' : true;
dataToSet.title ? setData += , title = '${dataToSet.title}' : true;
console.log(UPDATE article SET&nbsp;${setData}&nbsp;where 1&nbsp;${conditions});
dbConfig.getDB().query(UPDATE article SET&nbsp;${setData}&nbsp;where 1&nbsp;${conditions}, callback);
}
module.exports = {
getArticle : getArticle,
createArticle : createArticle,
deleteArticle : deleteArticle,
updateArticle : updateArticle,
getArticleDetail : getArticleDetail
}
</pre>

Now one create Services folder and add a file article.js for all the logic of API

<pre class="ql-syntax" spellcheck="false"> let async = require('async'),
parseString = require('xml2js').parseString;
 
let util = require('../Utilities/util'),
articleDAO = require('../DAO/articleDAO');
//config = require("../Utilities/config").config;
 
 
/**API to create the atricle */
let createArticle = (data, callback) => {
async.auto({
article: (cb) => {
var dataToSet = {
"category":data.category?data.category:'',
"title":data.title,
}
console.log(dataToSet);
articleDAO.createArticle(dataToSet, (err, dbData) => {
if (err) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.SERVER_BUSY });
return;
}
 
cb(null, { "statusCode": util.statusCode.OK, "statusMessage":util.statusMessage.DATA_UPDATED,"result":dataToSet });
});
}
//]
}, (err, response) => {
callback(response.article);
});
}
 
/**API to update the article */
let updateArticle = (data,callback) => {
async.auto({
articleUpdate :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.PARAMS_MISSING })
return;
}
console.log('phase 1');
var criteria = {
id : data.id,
}
var dataToSet={
"category": data.category,
"title":data.title,
}
console.log(criteria,'test',dataToSet);
                    articleDAO.updateArticle(criteria, dataToSet, (err, dbData)=>{
                        if(err){
cb(null,{"statusCode":util.statusCode.FOUR_ZERO_ONE,"statusMessage":util.statusMessage.SERVER_BUSY});
                        return; 
                        }
                        else{
cb(null, { "statusCode": util.statusCode.OK, "statusMessage":util.statusMessage.DATA_UPDATED,"result":dataToSet });                        
                        }
                    });
}
}, (err,response) => {
callback(response.articleUpdate);
});
}
 
/**API to delete the subject */
let deleteArticle = (data,callback) => {
console.log(data,'data to set')
async.auto({
removeArticle :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.PARAMS_MISSING })
return;
}
var criteria = {
id : data.id,
}
articleDAO.deleteArticle(criteria,(err,dbData) => {
if (err) {
console.log(err);
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.SERVER_BUSY });
return;
}
cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DELETE_DATA });
});
}
}, (err,response) => {
callback(response.removeArticle);
});
}
 
/***API to get the article list */
let getArticle = (data, callback) => {
async.auto({
article: (cb) => {
articleDAO.getArticle({},(err, data) => {
if (err) {
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage":util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data);
return;
});
}
}, (err, response) => {
callback(response.article);
})
}
 
/***API to get the article detail by id */
let getArticleById = (data, callback) => {
async.auto({
article: (cb) => {
let criteria = {
"id":data.id
}
articleDAO.getArticleDetail(criteria,(err, data) => {
if (err) {
console.log(err,'error----');
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage":util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data[0]);
return;
});
}
}, (err, response) => {
callback(response.article);
})
}
 
module.exports = {
createArticle : createArticle,
updateArticle : updateArticle,
deleteArticle : deleteArticle,
getArticle : getArticle,
getArticleById : getArticleById
};
</pre>

3. Create angular component for performing CRUD task of article

<pre class="ql-syntax" spellcheck="false">ng g component article
</pre>

Above command will generate all required files for build article component and also automatically added this component to app.module.ts.

<pre class="ql-syntax" spellcheck="false">create src/app/article/article.component.css (0 bytes)
create src/app/article/article.component.html (23 bytes)
create src/app/article/article.component.spec.ts (614 bytes)
create src/app/article/article.component.ts (321 bytes)
update src/app/app.module.ts (390 bytes)
</pre>

Now we need to add HttpClientModule to app.module.ts. Open and edit src/app/app.module.ts then add this import. And add it to @NgModule imports after BrowserModule. Now our app.module.ts will have following code:

<pre class="ql-syntax" spellcheck="false"> import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
 
import { AppComponent } from './app.component';
import { ArticleComponent } from './article.component';
import { ArticleService } from './article.service';
 
@NgModule({
imports: [
BrowserModule,
HttpModule,
ReactiveFormsModule
],
declarations: [
AppComponent,
ArticleComponent
],
providers: [
ArticleService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
</pre>

Now create a service file where we will make all the request to the server for CRUD operation. Command for creating service is ng g service artcle , for now I have just created a file named it article.service.ts. Let's have a look in the code inside this file.

<pre class="ql-syntax" spellcheck="false">import { Injectable } from '@angular/core';
import { Http, Response, Headers, URLSearchParams, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
 
import { Article } from './article';
 
@Injectable()
export class ArticleService {
//URL for CRUD operations
    articleUrl = "http://localhost:3000/article";
    //Create constructor to get Http instance
    constructor(private http:Http) {
    }
    
    //Fetch all articles
getAllArticles(): Observable<Article[]> {
return this.http.get(this.articleUrl+"/get-article")
              .map(this.extractData)
         .catch(this.handleError);
 
}
    //Create article
createArticle(article: Article):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: cpHeaders });
return this.http.post(this.articleUrl+"/create-article", article, options)
.map(success => success.status)
.catch(this.handleError);
}
    //Fetch article by id
getArticleById(articleId: string): Observable<Article> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        console.log(this.articleUrl +"/get-article-by-id?id="+ articleId);
        return this.http.get(this.articleUrl +"/get-article-by-id?id="+ articleId)
             .map(this.extractData)
             .catch(this.handleError);
}   
    //Update article
updateArticle(article: Article):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
return this.http.put(this.articleUrl +"/update-article", article, options)
.map(success => success.status)
.catch(this.handleError);
}
//Delete article    
deleteArticleById(articleId: string): Observable<number> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        return this.http.delete(this.articleUrl +"/delete-article?id="+ articleId)
             .map(success => success.status)
             .catch(this.handleError);
}   
    private extractData(res: Response) {
        let body = res.json();
return body;
}
private handleError (error: Response | any) {
        console.error(error.message || error);
        return Observable.throw(error.status);
}
}
</pre>

In the above file we have made all the http request for the CRUD operation. Observables of rxjs library has been used to handle the data fetching from http request.

 

Now let's move to the next file, article.component.ts. Here we have all the login part of the app. Let's have a look code inside this file:

<pre class="ql-syntax" spellcheck="false">import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
 
import { ArticleService } from './article.service';
import { Article } from './article';
 
@Component({
selector: 'app-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
//Component properties
allArticles: Article[];
statusCode: number;
requestProcessing = false;
articleIdToUpdate = null;
processValidation = false;
//Create form
articleForm = new FormGroup({
title: new FormControl('', Validators.required),
category: new FormControl('', Validators.required)   
});
//Create constructor to get service instance
constructor(private articleService: ArticleService) {
}
//Create ngOnInit() and and load articles
ngOnInit(): void {
     this.getAllArticles();
}
//Fetch all articles
 
getAllArticles() {
        this.articleService.getAllArticles()
         .subscribe(
data => this.allArticles = data,
                errorCode => this.statusCode = errorCode);
                
}
//Handle create and update article
onArticleFormSubmit() {
     this.processValidation = true;
     if (this.articleForm.invalid) {
     return; //Validation failed, exit from method.
     }
     //Form is valid, now perform create or update
this.preProcessConfigurations();
     let article = this.articleForm.value;
     if (this.articleIdToUpdate === null) {
     //Generate article id then create article
this.articleService.getAllArticles()
     .subscribe(articles => {
            
         //Generate article id    
         let maxIndex = articles.length - 1;
         let articleWithMaxIndex = articles[maxIndex];
         let articleId = articleWithMaxIndex.id + 1;
         article.id = articleId;
         console.log(article,'this is form data---');
         //Create article
    this.articleService.createArticle(article)
             .subscribe(successCode => {
                    this.statusCode = successCode;
                    this.getAllArticles();  
                    this.backToCreateArticle();
                 },
                 errorCode => this.statusCode = errorCode
             );
         });        
     } else {
  //Handle update article
article.id = this.articleIdToUpdate;        
     this.articleService.updateArticle(article)
     .subscribe(successCode => {
         this.statusCode = successCode;
                 this.getAllArticles();  
                    this.backToCreateArticle();
             },
         errorCode => this.statusCode = errorCode);  
     }
}
//Load article by id to edit
loadArticleToEdit(articleId: string) {
this.preProcessConfigurations();
this.articleService.getArticleById(articleId)
     .subscribe(article => {
            console.log(article,'poiuytre');
         this.articleIdToUpdate = article.id;
                    this.articleForm.setValue({ title: article.title, category: article.category });
                    this.processValidation = true;
                    this.requestProcessing = false;
         },
         errorCode => this.statusCode = errorCode);
}
//Delete article
deleteArticle(articleId: string) {
this.preProcessConfigurations();
this.articleService.deleteArticleById(articleId)
     .subscribe(successCode => {
         //this.statusCode = successCode;
                    //Expecting success code 204 from server
                    this.statusCode = 204;
                 this.getAllArticles();  
                 this.backToCreateArticle();
             },
         errorCode => this.statusCode = errorCode);
}
//Perform preliminary processing configurations
preProcessConfigurations() {
this.statusCode = null;
     this.requestProcessing = true;
}
//Go back from update to create
backToCreateArticle() {
this.articleIdToUpdate = null;
this.articleForm.reset(); 
     this.processValidation = false;
}
}
</pre>

Now we have to show the task over browser, So lets have a look inside article.component.html file.

<pre class="ql-syntax" spellcheck="false"><h1 class="text-center">Angular 7 CRUD Demo App</h1>
<h3 class="text-center" *ngIf="articleIdToUpdate; else create">
Update Article for Id: {{articleIdToUpdate}}
</h3>
<ng-template #create>
<h3 class="text-center"> Create New Article </h3>
</ng-template>
<div>
<form [formGroup]="articleForm" (ngSubmit)="onArticleFormSubmit()">
<table class="table-striped" style="margin:0 auto;">
<tr><td>Enter Title</td><td><input formControlName="title">
   <label *ngIf="articleForm.get('title').invalid && processValidation" [ngClass] = "'error'"> Title is required. </label>
 </td></tr>
<tr><td>Enter Category</td><td><input formControlName="category">
   <label *ngIf="articleForm.get('category').invalid && processValidation" [ngClass] = "'error'">Category is required. </label>
  </td></tr>  
<tr><td colspan="2">
   <button class="btn btn-default" *ngIf="!articleIdToUpdate">CREATE</button>
    <button class="btn btn-default" *ngIf="articleIdToUpdate">UPDATE</button>
   <button (click)="backToCreateArticle()" *ngIf="articleIdToUpdate">Go Back</button>
  </td></tr>
</table>
</form>
<br/>
<div class="text-center" *ngIf="statusCode; else processing">
<div *ngIf="statusCode === 201" [ngClass] = "'success'">
   Article added successfully.
</div>
<div *ngIf="statusCode === 409" [ngClass] = "'success'">
Article already exists.
</div>   
<div *ngIf="statusCode === 200" [ngClass] = "'success'">
Article updated successfully.
</div>   
<div *ngIf="statusCode === 204" [ngClass] = "'success'">
Article deleted successfully.
</div>   
<div *ngIf="statusCode === 500" [ngClass] = "'error'">
Internal Server Error.
</div> 
</div>
<ng-template #processing>
  <img *ngIf="requestProcessing" src="assets/images/loading.gif">
</ng-template>
</div>
<h3 class="text-center">Article List</h3>
<table class="table-striped" style="margin:0 auto;" *ngIf="allArticles">
<tr><th> Id</th> <th>Title</th><th>Category</th><th></th><th></th></tr>
<tr *ngFor="let article of allArticles" >
<td>{{article.id}}</td> <td>{{article.title}}</td> <td>{{article.category}}</td>
  <td><button class="btn btn-default" type="button"(click)="loadArticleToEdit(article.id)">Edit</button> </td>
  <td><button class="btn btn-default" type="button"(click)="deleteArticle(article.id)">Delete</button></td>
</tr>
</table>
</pre>

Now since I have created server and client two separate folder for nodejs and angular task. So will run both the apps with npm start over two tabs of terminal.

On the browser, over link <a href="http://localhost:4200." target="_blank">http://localhost:4200.</a> App will look like below

 

That’s all for now. Thank you for reading and I hope this post will be very helpful for creating CRUD operations with angular7,node.js & mysql.



================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on <a href="https://l.morioh.com/b0a3f595aa?r=https://www.facebook.com/angular4u" title="" target="_blank">Facebook</a> | <a href="https://l.morioh.com/b0a3f595aa?r=https://twitter.com/codek_tv" title="" target="_blank">Twitter</a>

Learn More

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/H1jE_tD3l" title="" target="_blank">Angular 8 (formerly Angular 2) - The Complete Guide</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/HJigQzgZx" title="" target="_blank">Learn and Understand AngularJS</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/ry7Ey9yKW" title="" target="_blank">The Complete Angular Course: Beginner to Advanced</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/rkjpGfa5W" title="" target="_blank">Angular Crash Course for Busy Developers</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/SkdU19JFZ" title="" target="_blank">Angular Essentials (Angular 2+ with TypeScript)</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/B1oHaY8cM" title="" target="_blank">Angular (Full App) with Angular Material, Angularfire & NgRx</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/Skf7ILFw3l" title="" target="_blank">Angular & NodeJS - The MEAN Stack Guide</a>



Wrapping CommonJS library in Angular 8 using Mark.js

Wrapping CommonJS library in Angular 8 using Mark.js

Introduction

Time to time on my daily tasks I have to implement some functionality that was already implemented by someone previously in a neat vanillaJS library, but… no Angular version or even ES6 module of it is available to be able to easily grab it into your Angular 8 application.

Yes, you can attach this lib in index.html with script tag but from my point of view, it hardens maintainability. Also, you should do the same for another Angular project where you might use it.

Much better for is create Angular wrapper directive (or component) and publish it as npm package so everyone (and you of course:) can easily re-use it in another project.

One of such libraries is mark.js — quite solid solution for highlighting search text inside a specified webpage section.
Wrapping CommonJS library in Angular 8 using Mark.js

<figcaption class="av ej me mf hr do dm dn mg mh aq cv">mark.js</figcaption>

How mark.js works

In original implementation mark.js can be connected to a project in two ways:

$ npm install mark.js --save-dev// in JS code
const Mark = require('mark.js');
let instance = new Mark(document.querySelector("div.context"));
instance.mark(keyword [, options]);OR<script src="vendor/mark.js/dist/mark.min.js"></script>// in JS code
let instance = new Mark(document.querySelector("div.context"));
instance.mark(keyword [, options]);

And the result looks like this:
Wrapping CommonJS library in Angular 8 using Mark.js

<figcaption class="av ej me mf hr do dm dn mg mh aq cv">mark.js run result (taken from [official Mark.js page](https://markjs.io/configurator.html))</figcaption>

You can play with it more on mark.js configurator page.

But can we use it in Angular way? Say, like this

// some Angular module
imports: [
...
MarkjsModule // imports markjsHighlightDirective
...
]// in some component template
<div class="content_wrapper" 
     [markjsHighlight]="searchValue"
     [markjsConfig]="config"
>

Let's also add some additional functionality. Say, scroll content_wrapper to first highlighted word:

<div class="content_wrapper" 
     [markjsHighlight]="searchText"
     [markjsConfig]="config"
     [scrollToFirstMarked]="true"
>

Now let's implement and publish Angular library with a demo application that will contain markjsHighlightDirective and its module.
We will name it ngx-markjs.

Planning Angular project structure

To generate an Angular project for our lib we will use Angular CLI.

npm install -g @angular/cli

Now let's create our project and add ngx-markjs lib to it:

ng new ngx-markjs-demo --routing=false --style=scss
// a lot of installations goes herecd ngx-markjs-demong generate lib ngx-markjs

And now lets add markjsHighlightDirective starter to our ngx-markjs lib

ng generate directive markjsHighlight --project=ngx-markjs

After deleting ngx-markjs.component.ts and ngx-markjs.service.ts in projects/ngx-markjs/src/lib/ folder which were created automatically by Angular CLI we will get next directory structure for our project:
Wrapping CommonJS library in Angular 8 using Mark.js

<figcaption class="av ej me mf hr do dm dn mg mh aq cv">ngx-markjs-demo project with ngx-markjs lib</figcaption>

To conveniently build our library lets add two more lines in a project package.json file to scripts section:

"scripts": {
  "ng": "ng",
  "start": "ng serve --port 4201",
  "build": "ng build",
  "build:ngx-markjs": "ng build ngx-markjs && npm run copy:lib:dist", 
  "copy:lib:dist": "cp -r ./projects/ngx-markjs/src ./dist/ngx-markjs/src",
  "test": "ng test",
  "lint": "ng lint",
  "e2e": "ng e2e"
},

build:ngx-markjs — runs build for ngx-markjs library (but not for parent demo project)

copy:lib:dist — it is convenient to have source files in npm packages as well, so this command will copy library sources to /dist/ngx-markjs folder (where compiled module will be placed after build:ngx-markjs command).

Now time to add implementation code!

*Remark: official Angular documentation about creating libraries recommends generating starter without main parent project, like this:
ng new my-workspace — create-application=false
But I decided to keep the main project and make it a demo application just for my convenience.

Connecting commonJS lib into Angular app

We need to do a few preparational steps before we start implementing our directive:

#1. Load mark.js

Mark.js library which we wan to wrap is provided in CommonJS format.

There are two ways to connect script in CommonJS script:

a) Add it with script tag to index.html:

<script src="vendor/mark.js/dist/mark.min.js"></script>

b) Add it to angular.json file in a project root so Angular builder will grab and applied it (as if it was included with a script tag)

"sourceRoot": "src",
"prefix": "app",
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": {
      "outputPath": "dist/ngx-markjs-demo",
      "index": "src/index.html",
      "main": "src/main.ts",
      "polyfills": "src/polyfills.ts",
      "tsConfig": "tsconfig.app.json",
      "aot": false,
      "assets": [
        "src/favicon.ico",
        "src/assets"
      ],
      "styles": [
        "src/styles.scss"
      ],
      "scripts": []
    },

#2. Adding mark.js to lib package.json

Now we should add mark.js lib as a dependency to our library package.json in <root>/projects/ngx-markjs/src folder (don't mix it up with src/package.json — file for main parent project).
We can add it as peerDependencies section — in that case, you should install mark.js manually prior to installing our wrapper package.

Or we can add mark.js to dependencies section — then mark.js package will be installed automatically when you run npm i ngx-markjs.

You can read more about the difference between package.json dependencies and peerDependencies in this great article.

#3. Get entity with require call.

const Mark = require('mark.js');

In our case, I would prefer to use require since mark.js code should be present only inside markjsHighlight lib module but not in whole application (until we use actually it there).

Small remark: some tslint configurations prevent using require to stimulate using es6 modules, so in that case just wrap require with _/ tslint: disabled /_ comment. Like this:

/* tslint:disable */
const Mark = require('mark.js');
/* tslint:enable */

The project is ready. Now it is time to implement our markjsHighlightDirective.

Wrapping mark.js in a directive

Ok, so lets plan how our markjsHighlightDirective will work:

  1. It should be applied to the element with content — to get HTML element content where the text will be searched. (markjsHighlight input)
  2. It should accept mark.js configuration object (markjsConfig input)
  3. And we should be able to switch on and off 'scroll to marked text' feature (scrollToFirstMarked input)

For example:

<div class="content_wrapper" 
     [markjsHighlight]="searchText"
     [markjsConfig]="config"
     [scrollToFirstMarked]="true"
>

Now it is time to implement these requirements.

Adding mark.js to the library

Install mark.js to our project

npm install mark.js

And create its instance in a projects/ngx-markjs/src/lib/markjs-highlight.directive.ts file:

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


declare var require: any;
const Mark = require('mark.js');


@Directive({
  selector: '[markjsHighlight]'
})
export class MarkjsHighlightDirective {

  constructor() {}

}

To prevent Typescript warnings — I declared require global variable.

Creating a basic directive starter

The very first starter for MarkjsHighlightDirective will be

@Directive({
  selector: '[markjsHighlight]' // our directive
})
export class MarkjsHighlightDirective implements OnChanges {

  @Input() markjsHighlight = '';  // our inputs
  @Input() markjsConfig: any = {};
  @Input() scrollToFirstMarked: boolean = false;

  @Output() getInstance = new EventEmitter<any>();

  markInstance: any;

  constructor(
    private contentElementRef: ElementRef, // host element ref
    private renderer: Renderer2 // we will use it to scroll
  ) {
  }

  ngOnChanges(changes) {  //if searchText is changed - redo marking    if (!this.markInstance) { // emit mark.js instance (if needeed)
      this.markInstance = new Mark(this.contentElementRef.nativeElement);
      this.getInstance.emit(this.markInstance);
    }

    this.hightlightText(); // should be implemented    if (this.scrollToFirstMarked) {
      this.scrollToFirstMarkedText();// should be implemented
    }    
  }
}

Ok, so let's go through this starter code:

  1. We defined three inputs for searchText value, config and scrolling on/off functionality (as we planned earlier)
  2. ngOnChanges lifeCycle hook emits instance of Mark.js to parent component (in case you want to implement some additional Mark.js behavior)
    Also, each time searchText is changed we should redo text highlight (since search text is different now) — this functionality will be implemented in this.hightlightText method.
    And if scrollToFirstMarked is set to true — then we should run this.scrollToFirstMarkedText.

Implementing highlight functionality

Our method this.hightlightText should receive searchText value, unmark previous search results and do new text highlighting. It can be successfully done with this code:

hightlightText() {
  this.markjsHighlight = this.markjsHighlight || '';   if (this.markjsHighlight && this.markjsHighlight.length <= 2) {
    this.markInstance.unmark();
    return;  } else {    this.markInstance.unmark({
      done: () => {
        this.markInstance.mark((this.markjsHighlight || ''), this.markjsConfig);
      }
    });
  }
}

Code is self-explanatory: we check if markjsHighlight valur is not null or undefined (because with these values Mark.js instances throw the error).

Then check for text length. If it is just one letter or no text at all — we unmark text and return;

Otherwise, we unmark previously highlighted text and start new highlighting process.

Implementing a "scroll to first marked result" feature

One important remark here before we start implementing scroll feature: content wrapper element, where we apply our directive to should have css position set other than static (for example_position: relative_). Otherwise offset to be scrolled to will be calculated improperly.

OK, lets code this.scrollToFirstMarkedText method:

constructor(
  private contentElementRef: ElementRef,
  private renderer: Renderer2
) {
}
....scrollToFirstMarkedText() {
  const content = this.contentElementRef.nativeElement;// calculating offset to the first marked element
  const firstOffsetTop = (content.querySelector('mark') || {}).offsetTop || 0;   this.scrollSmooth(content, firstOffsetTop); // start scroll
}

scrollSmooth(scrollElement, firstOffsetTop) {
  const renderer = this.renderer;

  if (cancelAnimationId) {
    cancelAnimationFrame(cancelAnimationId);
  }
  const currentScrollTop = scrollElement.scrollTop;
  const delta = firstOffsetTop - currentScrollTop;

  animate({
    duration: 500,
    timing(timeFraction) {
      return timeFraction;
    },
    draw(progress) {
      const nextStep = currentScrollTop + progress * delta;     // set scroll with Angular renderer
     renderer.setProperty(scrollElement, 'scrollTop', nextStep);
    }
  });
}...
let cancelAnimationId;// helper function for smooth scroll
function animate({timing, draw, duration}) {
  const start = performance.now();
  cancelAnimationId = requestAnimationFrame(function animate2(time) {
    // timeFraction goes from 0 to 1
    let timeFraction = (time - start) / duration;
    if (timeFraction > 1) {
      timeFraction = 1;
    }
    // calculate the current animation state
    const progress = timing(timeFraction);
    draw(progress); // draw it
    if (timeFraction < 1) {
      cancelAnimationId = requestAnimationFrame(animate2);
    }
  });
}

How it works:

  1. We get content wrapper element (injected in a constructor by Angular) and query for first highlighted text node (Mark.js to highlight text wrap it in <Mark></Mark> HTML element).
  2. Then start **this.scrollSmooth**function. scrollSmooth cancels previous scroll (if any), calculates scroll difference, delta (diff between current scroll position and offsetTop of marked element) and call an animated function which will calculate timings for smooth scrolling and do actual scroll (by calling renderer.setProperty(scrollElement, ‘scrollTop’, nextStep)).
  3. Animate function is a helper taken from a very good javascript learning tutorial site javscript.info.

Our directive is ready! You can take a look at a full code here.

The only thing we have to do yet is to add a directive to NgxMarkjsModule module:

import { NgModule } from '@angular/core';
import { MarkjsHighlightDirective } from './markjs-highlight.directive';



@NgModule({
  declarations: [MarkjsHighlightDirective],
  imports: [
  ],
  exports: [MarkjsHighlightDirective]
})
export class NgxMarkjsModule { }

Applying Result

Now let's use it in our demo application:

1. Import NgxMarkjsModule to app.module.ts:

...
import {NgxMarkjsModule} from 'ngx-markjs';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    NgxMarkjsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

2. I added some content to app.component.html and applied the directive to it:

<div class="search_input">
  <input placeholder="Search..." #search type="text">
</div>
<div class="content_wrapper"
     [markjsHighlight]="searchText$ | async"
     [markjsConfig]="searchConfig"
     [scrollToFirstMarked]="true"
>
  <p>Lorem ipsum dolor ssit amet, consectetur...a lot of text futher</p>

3. In app.component.ts we should subscribe to input change event and feed search text to markjsHighlight directive with async pipe:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit {
  title = 'ngx-markjs-demo';
  @ViewChild('search', {static: false}) searchElemRef: ElementRef;
  searchText$: Observable<string>;
  searchConfig = {separateWordSearch: false};

  ngAfterViewInit() {
    // create stream from inpout change event with rxjs 'from' function    this.searchText$ = fromEvent(this.searchElemRef.nativeElement, 'keyup').pipe(
      map((e: Event) => (e.target as HTMLInputElement).value),
      debounceTime(300),
      distinctUntilChanged()
    );
  }
}

Let's start it and take a look at result:

ng serve

Wrapping CommonJS library in Angular 8 using Mark.js

<figcaption class="av ej me mf hr do dm dn mg mh aq cv">It works!</figcaption>

We did it!
The last thing to do: we should publish our directive to npm registry:

npm login
npm build:ngx-markjs
cd ./dist/ngx-markjs
npm publish

And here it is in a registry: ngx-markjs.

Conclusion

Did you meet some neat vanillaJS library which you want to use in Angular? Now you know how to do that!

Pros

  1. Now we can easily import our directive in Angular 8 project.
  2. Additional scroll functionality is quite neat — use it to improve user experience.

Cons

  1. Possibly mark.js implemented only for a browser. So if you plan to use it in some other platforms (Angular allows it — read more about it here) — it may not work.

Related links:

  1. Mark.js
  2. ngx-markjs github repo.

Angular 8 Forms Tutorial - Reactive Forms Validation Example

Angular 8 Forms Tutorial - Reactive Forms Validation Example

The example is a simple registration form with pretty standard fields for title, first name, last name, email, password, confirm password and an accept Ts & Cs checkbox. All fields are required including the checkbox, the email field must be a valid email address and the password field must have a min length of 6. There's also a custom validator called MustMatch which is used to validate that the confirm password and password fields match.

I've setup the form to validate on submit rather than as soon as each field is changed, this is implemented with a submitted property in the app component that is set to true when the form is submitted for the first time, and reset to false if the cancel button is clicked.

Styling of the example is all done with Bootstrap 4.3 CSS.

See on StackBlitz at https://stackblitz.com/edit/angular-8-reactive-form-validation

Reactive Forms Validation App Component

The app component defines the form fields and validators for our registration form using an Angular FormBuilder to create an instance of a FormGroup that is stored in the registerForm property. The registerForm is then bound to the form in the app template below using the [formGroup] directive.

I also added a getter f as a convenience property to make it easier to access form controls from the template. So for example you can access the confirmPassword field in the template using f.confirmPassword instead of registerForm.controls.confirmPassword.

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

// import custom validator to validate that password and confirm password fields match
import { MustMatch } from './_helpers/must-match.validator';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    registerForm: FormGroup;
    submitted = false;

    constructor(private formBuilder: FormBuilder) { }

    ngOnInit() {
        this.registerForm = this.formBuilder.group({
            title: ['', Validators.required],
            firstName: ['', Validators.required],
            lastName: ['', Validators.required],
            email: ['', [Validators.required, Validators.email]],
            password: ['', [Validators.required, Validators.minLength(6)]],
            confirmPassword: ['', Validators.required],
            acceptTerms: [false, Validators.requiredTrue]
        }, {
            validator: MustMatch('password', 'confirmPassword')
        });
    }

    // convenience getter for easy access to form fields
    get f() { return this.registerForm.controls; }

    onSubmit() {
        this.submitted = true;

        // stop here if form is invalid
        if (this.registerForm.invalid) {
            return;
        }

        // display form values on success
        alert('SUCCESS!! :-)\n\n' + JSON.stringify(this.registerForm.value, null, 4));
    }

    onReset() {
        this.submitted = false;
        this.registerForm.reset();
    }
}

Reactive Forms Validation App Template

The app component template contains all the html markup for displaying the example registration form in your browser. The form element uses the [formGroup] directive to bind to the registerForm FormGroup in the app component above.

The form binds the form submit event to the onSubmit() handler in the app component using the Angular event binding (ngSubmit)="onSubmit()". Validation messages are displayed only after the user attempts to submit the form for the first time, this is controlled with the submitted property of the app component.

The cancel button click event is bound to the onReset() handler in the app component using the Angular event binding (click)="onReset()".

<!-- main app container -->
<div class="card m-3">
    <h5 class="card-header">Angular 8 Reactive Form Validation</h5>
    <div class="card-body">
        <form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
            <div class="form-row">
                <div class="form-group col">
                    <label>Title</label>
                    <select formControlName="title" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.title.errors }">
                        <option value=""></option>
                        <option value="Mr">Mr</option>
                        <option value="Mrs">Mrs</option>
                        <option value="Miss">Miss</option>
                        <option value="Ms">Ms</option>
                    </select>
                    <div *ngIf="submitted && f.title.errors" class="invalid-feedback">
                        <div *ngIf="f.title.errors.required">Title is required</div>
                    </div>
                </div>
                <div class="form-group col-5">
                    <label>First Name</label>
                    <input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
                    <div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
                        <div *ngIf="f.firstName.errors.required">First Name is required</div>
                    </div>
                </div>
                <div class="form-group col-5">
                    <label>Last Name</label>
                    <input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
                    <div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
                        <div *ngIf="f.lastName.errors.required">Last Name is required</div>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="text" formControlName="email" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.email.errors }" />
                <div *ngIf="submitted && f.email.errors" class="invalid-feedback">
                    <div *ngIf="f.email.errors.required">Email is required</div>
                    <div *ngIf="f.email.errors.email">Email must be a valid email address</div>
                </div>
            </div>
            <div class="form-row">
                <div class="form-group col">
                    <label>Password</label>
                    <input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
                    <div *ngIf="submitted && f.password.errors" class="invalid-feedback">
                        <div *ngIf="f.password.errors.required">Password is required</div>
                        <div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div>
                    </div>
                </div>
                <div class="form-group col">
                    <label>Confirm Password</label>
                    <input type="password" formControlName="confirmPassword" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.confirmPassword.errors }" />
                    <div *ngIf="submitted && f.confirmPassword.errors" class="invalid-feedback">
                        <div *ngIf="f.confirmPassword.errors.required">Confirm Password is required</div>
                        <div *ngIf="f.confirmPassword.errors.mustMatch">Passwords must match</div>
                    </div>
                </div>
            </div>
            <div class="form-group form-check">
                <input type="checkbox" formControlName="acceptTerms" id="acceptTerms" class="form-check-input" [ngClass]="{ 'is-invalid': submitted && f.acceptTerms.errors }" />
                <label for="acceptTerms" class="form-check-label">Accept Terms & Conditions</label>
                <div *ngIf="submitted && f.acceptTerms.errors" class="invalid-feedback">Accept Ts & Cs is required</div>
            </div>
            <div class="text-center">
                <button class="btn btn-primary mr-1">Register</button>
                <button class="btn btn-secondary" type="reset" (click)="onReset()">Cancel</button>
            </div>
        </form>
    </div>
</div>

Reactive Forms Custom "Must Match" Validator

The custom MustMatch validator is used in this example to validate that both of the password fields - password and confirmPassword - are matching. However it can be used to validate that any pair of fields is matching (e.g. email and confirm email fields).

It works slightly differently than a typical custom validator because I'm setting the error on the second field instead of returning it to be set on the formGroup. I did it this way because I think it makes the template a bit cleaner and more intuitive, the mustMatch validation error is displayed below the confirmPassword field so I think it makes sense that the error is attached the the confirmPassword form control.

import { FormGroup } from '@angular/forms';

// custom validator to check that two fields match
export function MustMatch(controlName: string, matchingControlName: string) {
    return (formGroup: FormGroup) => {
        const control = formGroup.controls[controlName];
        const matchingControl = formGroup.controls[matchingControlName];

        if (matchingControl.errors && !matchingControl.errors.mustMatch) {
            // return if another validator has already found an error on the matchingControl
            return;
        }

        // set error on matchingControl if validation fails
        if (control.value !== matchingControl.value) {
            matchingControl.setErrors({ mustMatch: true });
        } else {
            matchingControl.setErrors(null);
        }
    }
}

Reactive Forms Validation App Module

There isn't much going on in the app module other than the standard stuff, the main thing you need to remember for using reactive forms in Angular is to import the ReactiveFormsModule from '@angular/forms' and include it in the imports array of the @NgModule decorator.

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

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

@NgModule({
    imports: [
        BrowserModule,
        ReactiveFormsModule
    ],
    declarations: [
        AppComponent
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

Angular 8 (formerly Angular 2) - The Complete Guide

Angular & NodeJS - The MEAN Stack Guide

The Web Developer Bootcamp

Angular 8 is coming

MEAN Stack Angular 8 CRUD Web Application

Angular 8 + Spring Boot 2.2: Build a CRUD App Today!

Best 17 Angular Libraries Every Angular Developers Should Know in 2019

React vs Angular vs Vue.js by Example

Best JavaScript Frameworks, Libraries and Tools to Use in 2019

Angular Authentication with JWT 

How to use Google Maps in Angular?

How to use Google Maps in Angular?

Google maps meet Angular. Having such a strong developer community and being used by thousands of developers around the world made Angular ideal for bringing most tools to the framework. bringing Google maps to Angular is one of them, it solved many problems for the Angular users. Manually adding Google maps to Angular is very difficult, so some people took it up upon themselves to make adding GMaps to your apps as easy as importing a module.

In this post, we will be looking at integrating Google Maps in your Angular application

The AGM library

This is the main library we will be using. The main library in the AGM is the @agm/core library. @agm/core contains solutions for the Google Maps JavaScript Core API.

To use this, we will first scaffold an Angular project:

ng new ng-maps

The above command will create a new Angular project.

Now, we install the @agm/core library:

npm i @agm/core

We need a component where we can display the map, to do that we scaffold a component:

g c map

This command will create map.component.ts in src/app/map/ folder.

We need to import the AgmModule in our AppModule:

...
import { AgmCoreModule } from '@agm/core';

@NgModule({
    declarations: [
        AppComponent,
        MapComponent
    ],
    imports: [
        BrowserModule,
        AgmCoreModule.forRoot({
            apiKey: environment.keys.gmap,
            libraries: ["places", "geometry"]
            /* apiKey is required, unless you are a premium customer, in which case you can use clientId */
        })
    ],
    providers: [
        ...
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

See, the AgmModule is imported and placed in the imports section. We need a GMaps key, to get it go to console.developers.google.com and follow the instructions.

After successfully obtaining the key, you can paste it here

...
        AgmCoreModule.forRoot({
            apiKey: "",
            libraries: ["places", "geometry"]
            /* apiKey is required, unless you are a premium customer, in which case you can use clientId */
        })
...

I added mine to the environment.ts file. For simplicity just add it to the apiKey property above.

We are done now. To display the map, we open up our map.component.ts and paste this:

<!-- map.component.html -->
<agm-map></agm-map>
// map.component.ts
@Component({
    ...
})
export class MapComponent {}

This will render the Google Map on our MapComponent.

Run ng serve, we will see it on our browser.

The agm-map selector tag is used to render the Google Maps on our components. It has inputs and outputs which we will see in the below sections.

Display a Selected Latitude and Longitude 🌍

The above code does nothing just displays a Map. Let’s make it display a selected location from a provided latitude and longitude position.

Remember, latitude and longitude is what is used to locate a position on a map, It is triangulated in the x-y plane. The latitude is in the x-plane while longitude is in the y-plane.

So when given a latitude and longitude, we trace from the x-axis and the y-axis, till they meet. The intersection is the location/position of the object or place.

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude"></agm-map>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785
        }
    }
}

interface Location {
    latitude: number;
    longitude: number
}

Map Type 🌍

Google Maps have options on how we want the map view displayed.

street view : This will show the streets on an eye-level, we can navigate/walk the streets with the arrow buttons

satellite: Displays the amp as seen by a satellite.

normal: The default map showed us by Google.

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType"></agm-map>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite"
        }
    }
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
}

Zooming 🔎

agm-map has an option to zoom in or out in the map view. We use the [zoom] input to accomplish that.

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom"></agm-map>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite",
            zoom: 5
        }
    }
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
}

We set the zoom depth to 5.

This has zoom levels:

  • 1: World
  • 5: Landmass/continent
  • 10: City
  • 15: Streets
  • 20: Buildings

Specifying a zoom level of 0-4 will show the map of the entire Earth. 5-9 will show the map in the continent at a higher resolution more than 0-5. Zoom level from 11 - 14 will show the cities in the map location at a higher resolution than the previous zoom level. 15-19 will show the streets and 20-~ will show the buildings at an eye level.

So our above example will show the location on the map on the landmass/continent level.

Placing a marker 📌

To place an indicator/marker📍 on the map to show the precise location, we will use the agm-marker. Markers are like pins on the map.

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker [latitude]="location.marker.lat" [longitude]="location.marker.lng"></agm-marker>
</agm-map>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite",
            zoom: 5,
            marker: {
                lat: -28.68352,
                lng: -147.20785
            }
        }
    }
}

interface Marker {
    lat: number;
    lng: number;
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
    marker: Marker;
}

A marker/pin will appear at the precise location of (-28.68352,-147.20785).

Adding a marker 📌

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker *ngFor="let marker of location.markers" [latitude]="marker.lat" [longitude]="marker.lng"></agm-marker>
</agm-map>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite",
            zoom: 5,
            markers: [
                {
                    lat: -28.68352,
                    lng: -147.20785
                }
            ]
        }
    }

    addMarker(lat: number, lng: number) {
        this.location.markers.push({
            lat,
            lng
        })
    }
}

interface Marker {
    lat: number;
    lng: number;
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
    markers: Array<Marker>;
}

In this example, when a user clicks the map, it adds a new marker to the map. We hold the markers in an array, the agm-marker iterates over the array and places them on the map with a pin/marker. When we click on the map, a new marker is pushed to the markers array. A pin/marker appears over the position clicked.

Selecting a marker

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker *ngFor="let marker of location.markers" [latitude]="marker.lat" [longitude]="marker.lng" (markerClick)="selectMarker($event)"></agm-marker>
</agm-map>
<div>
    Selected Marker is: Longitude {{selectedMarker.lat}} Latitude {{selectedMarker.lng}}
</div>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location
    selectedMarker: Marker

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite",
            zoom: 5,
            markers: [
                {
                    lat: -28.68352,
                    lng: -147.20785
                }
            ]
        }
    }

    addMarker(lat: number, lng: number) {
        this.location.markers.push({
            lat,
            lng
        })
    }

    selectMarker(event) {
        this.selectedMarker = {
            lat: event.latitude,
            lng: event.longitude
        }
    }
}

interface Marker {
    lat: number;
    lng: number;
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
    markers: Array<Marker>;
}

Dragging marker

We can click, hold and drag a marker around.

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker 
    *ngFor="let marker of location.markers" [latitude]="marker.lat" 
    [longitude]="marker.lng" 
    (markerClick)="selectMarker($event)"     [markerDraggable]="true"
    (dragEnd)="markerDragEnd(coords, $event)"
></agm-marker>
</agm-map>
<div>
    Selected Marker is: Longitude {{selectedMarker.lat}} Latitude {{selectedMarker.lng}}
</div>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location
    selectedMarker: Marker

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite",
            zoom: 5,
            markers: [
                {
                    lat: -28.68352,
                    lng: -147.20785
                }
            ]
        }
    }

    addMarker(lat: number, lng: number) {
        this.location.markers.push({
            lat,
            lng
        })
    }

    selectMarker(event) {
        this.selectedMarker = {
            lat: event.latitude,
            lng: event.longitude
        }
    }

    markerDragEnd(coords: any, $event: MouseEvent) {
        this.location.latitude = coords.latitude
        this.location.longitude = coodrs.longitude
    }
}

interface Marker {
    lat: number;
    lng: number;
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
    markers: Array<Marker>;
}

We use the [markerDraggable]="true" to make the marker draggable. Then, we have the event (dragEnd)="markerDragEnd(coords, $event)", this event is fired when the user stops dragging the marker.

Labelling a marker

We can label a marker, i.e attach a name or description to a marker. It will be a name hovering above the marker, to identify the marker.

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker 
    *ngFor="let marker of location.markers" [latitude]="marker.lat" 
    [longitude]="marker.lng" 
    (markerClick)="selectMarker($event)"     [markerDraggable]="true"
    (dragEnd)="markerDragEnd(coords, $event)"
    [label]="marker.label"
></agm-marker>
</agm-map>
<div>
    Selected Marker is: Longitude {{selectedMarker.lat}} Latitude {{selectedMarker.lng}}
</div>
// map.component.ts
@Component({
    ...
})
export class MapComponent implements OnInit{
    location: Location
    selectedMarker: Marker

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite",
            zoom: 5,
            markers: [
                {
                    lat: -28.68352,
                    lng: -147.20785,
                    label: "new york"
                }
            ]
        }
    }

    addMarker(lat: number, lng: number) {
        this.location.markers.push({
            lat,
            lng,
            label: Date.now().toLocaleString()
        })
    }
    ...
}

interface Marker {
    lat: number;
    lng: number;
    label: string;
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
    markers: Array<Marker>;
}

We used the [label] input to add label to the marker. We added a label property to the Marker interface, so we changed our code to include the new addition. Our first marker will display new york on top of the marker, others will have Date it was created on top of them.

Now, it would be nice if the names of the markers will be shown on their heads instead of dates they were created. We use geocoding.

We need to install googlemaps library:

npm i googlemaps

We import it in our MapComponent and declare a google var:

import {} from "googlemaps"
declare var google: any;
...

Then, our code:

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker 
    *ngFor="let marker of location.markers" [latitude]="marker.lat" 
    [longitude]="marker.lng" 
    (markerClick)="selectMarker($event)"     [markerDraggable]="true"
    (dragEnd)="markerDragEnd(coords, $event)"
    [label]="marker.label"
></agm-marker>
</agm-map>
<div>
    Selected Marker is: Longitude {{selectedMarker.lat}} Latitude {{selectedMarker.lng}}
</div>
// map.component.ts
...
@Component({
    ...
})
export class MapComponent implements OnInit {
    location: Location
    selectedMarker: Marker

    ngOnInit() {
        this.location = {
            latitude: -28.68352,
            longitude: -147.20785,
            mapType: "satelite",
            zoom: 5,
            markers: [
                {
                    lat: -28.68352,
                    lng: -147.20785,
                    label: this.getAddress(-28.68352, -147.20785)
                }
            ]
        }
    }

    addMarker(lat: number, lng: number) {
        this.location.markers.push({
            lat,
            lng,
            label: this.getAddress(lat, lng)
        })
    }

  getAddress(lat, lng) {
    const geocoder = new google.maps.Geocoder();
    var latlng = new google.maps.LatLng(lat, lng);
    const request: google.maps.GeocoderRequest = {
      location: latlng
    };
    geocoder.geocode(request, (results, status) => {
      this.ngZone.run(() => {
        const address = results[0].formatted_address;
        return address
      });
    });
  }    
    ...
}

interface Marker {
    lat: number;
    lng: number;
    label: string;
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
    markers: Array<Marker>;
}

The getAddress method uses the latitude and longitude passed to it to get the address of the place. See, we modified the addMarker method, so it calls the getAddress method to assign the marker address to the label property.

We constructed a geocoder object, then set a GeoCoder request with the lat and lng passed, finally, we called the geocode method in the geocoder, and in the function callback we got the address of the location.

Displaying our current position 🎌🌍

Here, let’s make it display our current position:

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker 
    *ngFor="let marker of location.markers" [latitude]="marker.lat" 
    [longitude]="marker.lng" 
    (markerClick)="selectMarker($event)"     [markerDraggable]="true"
    (dragEnd)="markerDragEnd(coords, $event)"
    [label]="marker.label"
></agm-marker>
</agm-map>
<div>
    Selected Marker is: Longitude {{selectedMarker.lat}} Latitude {{selectedMarker.lng}}
</div>
// map.component.ts
...
@Component({
    ...
})
export class MapComponent implements OnInit {
    location: Location
    selectedMarker: Marker

    ngOnInit() {
        this.setCurrentPosition()
    }

    addMarker(lat: number, lng: number) {
        this.location.markers.push({
            lat,
            lng,
            label: this.getAddress(lat, lng)
        })
    }
    setCurrentPosition() {
        if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(position => {
            const {latitude, longitude} = position
            this.location = {
                latitude,
                longitude,
                mapType: "satelite",
                zoom: 14,
                markers: [
                    {
                        lat: longitude,
                        lng: latitude,
                        label: "My current position"
                    }
                ]
            }
        });
        } else {
        alert("Geolocation is not supported by this browser, please use google chrome.");
        }
    }
    ...
}

interface Marker {
    lat: number;
    lng: number;
    label: string;
}

interface Location {
    latitude: number;
    longitude: number;
    mapType: ?string;
    zoom: ?number;
    markers: ?Array<Marker>;
}

Now, we have set the component to display our current position on the map when loaded. How did we do it? Simple, we used the navigator object. The navigator has a geolocation object which we use to call the getCurrentPosition() function, this function accepts a function callback which it calls with the location object. See our function callback will receive the location in the position parameter.

Setting direction

We can plot directions on a map. This we do with the help of agm-direction.

First, we install it:

npm i agm-direction

Next, we import the modules:

// app.module.ts
...
import { AgmDirectionModule } from 'agm-direction';

@NgModule({
  ...
  imports: [
    ...
    AgmDirectionModule
  ]
})
export class AppModule { }

So, now to plot direction from origin Taipei Main Station to destination Taiwan Presidential Office:

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <!-- ... -->
    <agm-direction 
        [origin]="origin" 
        [destination]="destination"
    ></agm-direction>
</agm-map>
<!-- ... -->
// map.component.ts
...
@Component({
    ...
})
export class MapComponent implements OnInit {
    ...
    origin: any;
    destination: any;

    ngOnInit() {
        this.origin = { 
            lat: 24.799448, 
            lng: 120.979021 
        };
        this.destination = { 
            lat: 24.799524, 
            lng: 120.975017 
        };
    }
    ...
}
...

Calculating distance between points

We can calculate the distance between points on the map.

To calculate the distance between Taipei Main Station and destination Taiwan Presidential Office, we do this:

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <!-- ... -->
    <agm-direction 
        [origin]="origin" 
        [destination]="destination"
    ></agm-direction>
</agm-map>
<div>
    Distance: {{distance}}
</div>
<!-- ... -->
// map.component.ts
...
@Component({
    ...
})
export class MapComponent implements OnInit {
    ...
    origin: any;
    destination: any;
    distance: Number;

    ngOnInit() {
        this.origin = { 
            lat: 24.799448, 
            lng: 120.979021 
        };
        this.destination = { 
            lat: 24.799524, 
            lng: 120.975017 
        };
        this.distance = this.calculatedistance(this.origin, this.destination)
    }

    // calculate the distances from point1 to point2
    calculateDistance(point1, point2) {
        const p1 = new google.maps.LatLng(
        point1.lat,
        point1.lng
        );
        const p2 = new google.maps.LatLng(
        point2.lat,
        point2.lng
        );
        return (
        google.maps.geometry.spherical.computeDistanceBetween(p1, p2)/1000
        ).toFixed(2);
    }
    ...
}
...

We have a method calculateDistance that computes the distance between two points passed to it. We construct LatLng objects of the two points and pass it to google.maps.geometry.spherical.computeDistanceBetween(...). This returns the distance between the two points.

Rendering info window

We can place an info window over a marker. This is how we do it:

<!-- map.component.html -->
<agm-map [latitude]="location.latitude" [longitude]="location.longitude" [mapTypeId]="location.mapType" [zoom]="location.zoom" (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <agm-marker 
    *ngFor="let marker of location.markers" [latitude]="marker.lat" 
    [longitude]="marker.lng" 
    (markerClick)="selectMarker($event)"     [markerDraggable]="true"
    (dragEnd)="markerDragEnd(coords, $event)"
    [label]="marker.label"
>
         <agm-info-window [disableAutoPan]="true">{{marker.label}}</agm-info-window>
</agm-marker>
    <!-- ... -->
</agm-map>
<!-- ... -->

See it is placed inside the agm-marker.

Drawing a circle on the map

To draw a circle over a location we use the agm-circle tag.

It has the following inputs:

“latitude”: The latitude of the location

“longitude”: The longitude of the location

“clickable”: Sets the circle clickable or not

“draggable”: Sets the circle draggable or not

“editable”: Sets the circle editable or not

“fillColor”: The color of the circle

“fillOpacity”: The transparency of the circle

“radius”: The radius of the circle

<!-- map.component.html -->
<agm-map 
    [latitude]="location.latitude" 
    [longitude]="location.longitude" 
    [mapTypeId]="location.mapType" 
    [zoom]="location.zoom" 
    (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <!-- ... -->
    <agm-circle 
        [latitude]="location.latitude" 
        [longitude]="location.longitude" [clickable]="false" 
        [draggable]="true" 
        [editable]="false" 
        [fillColor]="green" 
        [fillOpacity]="0" 
        [radius]="circleRadius">
    </agm-circle>
</agm-map>
<!-- ... -->

Drawing a rectangle on the map

We use the agm-rectangle. We must provide the bounds:

“north”: The north bound

“east”: The east bound

“south”: The south bound

“west”: The west bound

<!-- map.component.html -->
<agm-map 
    [latitude]="location.latitude" 
    [longitude]="location.longitude" 
    [mapTypeId]="location.mapType" 
    [zoom]="location.zoom" 
    (mapClick)="addMarker($event.coords.lat, $event.coords.lng)">
    <!-- ... -->
    <agm-rectangle 
        [north]="north" 
        [south]="south" 
        [east]="east" 
        [west]="west"> 
    </agm-rectangle>
</agm-map>
<!-- ... -->

Conclusion

We pretty much exhausted Google Maps on Angular. There are still many more on this. Many inputs and outputs in the agm-circle, agm-rectangle, agm-info, agm-marker, agm-direction and agm-map. This should serve as a basic guide to Google Maps in Angular.

Write your comments below, if I missed a point or concept, spelling, grammar error, in short anything at all.

Thanks!!!