I recently ran into a small problem with the project I'm working on. The project is using Angular, and all access will be from desktop. But when the project was around 80% complete, 2 new requirements emerged:
- There should be a mobile app for:
- Perform 2-factor authentication;
- Get the user's geolocation.
As the project is using Angular, we chose to use Ionic Framework in mobile app, because as it is possible to use angular with it, it would be better for code reuse.
But that's not what happened. Our application was made as just a mono-project, which is the default for angular. Any code we tried to reuse, we had to copy from one project to another, and that's not code reuse.
Then came the idea of creating a library for this project. Where all the reusable code (such as http services, guards, pipes, custom validators, etc.) would be in the library, which can be used by two or more projects simultaneously.
That's why the idea of posting about it came up. But I have so much to talk about... So I've decided to split it in small pieces that will be posted in the next few days.
In the first part, I'll talk about how to create the workspace, how to create the projects within the workspace, how to create a service within the library and how to use this service in the project.
Creating a Workspace
Let's start making sure we have the @Angular/CLI most recently version, then we create a workspace, without any project inside:
npm i -g @angular/cli
ng new <workspace name> --create-application false
Let's go into the workspace folder and use the CLI again,
to generate the first application, which will be the workspace's default application.
We will also generate a library, which will be responsible for the HTTP requests of our project.
cd workspace
ng generate application app-name --routing --style=scss
ng generate library lib-name
Our file structure will be like this:
Each generated application will be a new folder inside the "projects" folder with its respective code, however, with the following difference between them:
- Applications generated with "ng generate application" have the package.json shared between them.
- Libraries generated with "ng generate library" have their own package.json.
Changing the Application
Let's modify our application and create a login page.
For this we will use the Angular CLI again and create a new component and its respective module:
ng generate module login --project=app-name --routing
ng generate component login --project=app-name
Change the login.component.html
file to be like that:
<form [formGroup]="form" (ngSubmit)="login()">
<div>
<label>Username</label>
<input type="text" formControlName="username"/>
</div>
<div>
<label>Password</label>
<input type="text" formControlName="password"/>
</div>
<button type="submit">
Login
</button>
</form>
login.component.ts
:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
form!: FormGroup;
constructor(
public formBuilder: FormBuilder,
) { }
ngOnInit(): void {
this.form = this.formBuilder.group({
username: null,
password: null,
})
}
login() {
// ...
}
}
Now that we have our simple login page, we need to have the service to authenticate.
Changing the library
Let's now modify our library.
First go to the library folder, which in our example is "lib-name",
and inside the "src" folder delete everything leaving only the files public-api.ts
and test.ts
.
Now let's generate the module responsible for HTTP in our lib:
ng g module http --project=lib-name
And modify the http.module.ts
file to look like this:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [],
imports: [
CommonModule,
HttpClientModule,
]
})
export class LibHttpModule { }
Let's also generate our AuthService:
ng generate service http/auth --project=lib-name
Let's use ReqRes as a mock API.
Modify auth.service.ts
to look like this:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpModule } from './http.module';
@Injectable({
providedIn: HttpModule, // Atention here
})
export class AuthService {
endpoint = 'https://reqres.in/api/login';
constructor(
public http: HttpClient
) { }
login(username: string, password: string) {
return this.http.post(this.endpoint, {
username, password
});
}
}
Look at line 6, where we changed providedIn
from root
to our newly generated module HttpModule
.
That way, whoever needs to use the AuthService
should import the HttpModule
.
This will be useful in later parts of this article.
Now modify public-api.ts
to export the module and service:
/*
* Public API Surface of lib-name
*/
export * from './lib/http/http.module';
export * from './lib/http/auth.service';
We have our login component in a project, and the authentication service in the library.
Now we need to communicate between them.
Using the library into the application
First, we need to modify the tsconfig.json
file from the root of our workspace.
The paths
property is responsible for mapping the directories, let's modify it to look like this:
{
// ...
"paths": {
"lib-name/*": [
"projects/lib-name/src/lib/*",
"projects/lib-name/src/lib/"
],
"lib-name": [
"dist/lib-name/*",
"dist/lib-name"
]
},
// ...
}
What do these settings do?
The first one (with "lib-name/*") is used during development.
To make sure that every import of lib files will use the not compiled version.
The second (with just "lib-name") is used during the project's build time.
Now we can go back into the app-name
project and modify the login.component.ts
file to import and use the AuthService
:
import { AuthService } from 'lib-name/http/auth.service';
// ...
export class LoginComponent implements OnInit {
constructor(
public authService: AuthService
) { }
// ...
login() {
const username = this.form.get('username')?.value;
const password = this.form.get('password')?.value;
this.authService.login(username, password).subscribe(
response => console.log('success', response),
error => console.log('error', error)
);
}
}
And login.module.ts
to import our HttpModule
:
// ...
import { HttpModule } from 'lib-name/http/http.module';
@NgModule({
// ...
imports: [
// ...
HttpModule,
]
})
export class LoginModule { }
And when we run our code, we'll see that the service is being called correctly:
Code
The code write in this tutorial is available at:
https://github.com/higorcavalcanti/blog-code-angular-wordspace-part1
Other parts of this tutorial
You've finished reading part 1 of the series: "Splitting a angular project into multiple using same workspace - Part 1".
Soon there will be other parts.
References:
The Best Way To Architect Your Angular Libraries:
https://tomastrajan.medium.com/the-best-way-to-architect-your-angular-libraries-87959301d3d3
Building an Ionic Multi App Project with Shared Angular Library:
https://medium.com/angular-in-depth/building-an-ionic-multi-app-project-with-shared-angular-library-c9fa0383fd71
Angular docs: File Structure
https://angular.io/guide/file-structure#multiple-projects
Angular docs: Angular CLI
https://angular.io/cli