Dividindo projeto angular em vários outros no mesmo Workspace - Parte 1

angular
biblioteca
workspace

Como utilizar o workspace do angular para criar projetos que sejam reutilizáveis - Parte 1


Recentemente passei por um pequeno problema no projeto que estou trabalhando. O projeto, feito em angular, tinha um escopo fechado, onde 100% dos acessos seriam feitos por desktop. Quando o projeto estava por volta de 80% concluído, surgiram 2 novos requisitos:

  • Deverá existir uma aplicação mobile para:
    • Realizar autenticação em 2 fatores;
    • Obter a geolocalização do usuário.

Como o projeto foi feito com angular, escolhemos fazer a aplicação com o Ionic Framework, pois como é possível utilizar angular com ele, seria melhor para o reaproveitamento de código.

Mas não foi oque aconteceu. A nossa aplicação foi feita como apenas um projeto, que é o padrão do angular. Todo código que tentamos reutilizar era necessário copiar de um projeto para o outro, e isso não é reutilização de código.

Surgiu a ideia então de criar uma biblioteca para este projeto. Onde todo código reutilizável do projeto (como serviços http, guards, pipes, custom validators, etc) ficaria na biblioteca, que pode ser utilizada por dois ou mais projetos em simultâneo.

Por isso surgiu a ideia de fazer um post no blog sobre isso. Porém é um assunto um pouco grande... e eu resolvi dividir em algumas partes que serão postadas nos próximos dias.

No post de hoje, que é a parte 1, irei falar sobre como criar o workspace, como criar os projetos dentro do workspace, como criar um service dentro da biblioteca e como utilizar esse service dentro do projeto.


Criando o Workspace

Vamos começar instalando a versão mais recente do @Angular/CLI e depois criando um workspace, mas sem criar a aplicação inicial:

npm i -g @angular/cli
ng new <nome do workspace> --create-application false

Vamos entrar na pasta do workspace e utilizar o CLI novamente, para gerar a primeira aplicação, que será a aplicação padrão do workspace.
Vamos gerar também uma biblioteca, que será responsável pelas requisições HTTP do nosso projeto.

cd workspace
ng generate application app-name --routing --style=scss
ng generate library lib-name

Ficando assim a estrutura de arquivos do nosso workspace:

Estrutura de arquivos

Cada aplicação gerada, será uma nova pasta dentro da pasta "projects" com o seu respectivo código, porém, com a seguinte diferença entre elas:

  • As aplicações geradas com "ng generate application" tem o package.json compartilhado entre elas.
  • As bibliotecas geradas com "ng generate library" tem seu próprio package.json.

Modificando a Aplicação

Vamos modificar nossa aplicação e criar uma página de login.
Para isso vamos utilizar novamente o Angular CLI e criar um novo componente com seu respectivo modulo:

ng generate module login --project=app-name --routing
ng generate component login --project=app-name

E modificaremos o login.component.html para ficar assim:

<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() {
    // ...
  }
}

Agora que temos nossa simples página de login, precisamos ter o serviço para realizar a autenticação.

Modificando a biblioteca

Vamos agora modificar a nossa biblioteca.
Primeiramente acesse a pasta da biblioteca, que no nosso exemplo é "lib-name", e dentro da pasta "src" apague tudo deixando apenas os arquivos public-api.ts e test.ts.
Agora vamos gerar o modulo responsável por HTTP da nossa lib:

ng g module http --project=lib-name

E vamos modificar o arquivo http.module.ts para ficar dessa maneira:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    HttpClientModule,
  ]
})
export class LibHttpModule { }

Vamos gerar também, o nosso AuthService:

ng generate service http/auth --project=lib-name

Vamos utilizar o ReqRes como mock de API. Modifique o auth.service.ts para ficar assim:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpModule } from './http.module';

@Injectable({
  providedIn: HttpModule, // Atenção aqui
})
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
    });
  }
}

Observe a linha 6, onde mudamos o providedIn de root para o nosso modulo recém gerado HttpModule. Dessa forma, quem precisar utilizar o AuthService deverá importar o HttpModule. Esse é um recurso que será útil nas próximas partes desse artigo.

Agora modifique o public-api.ts para exportar o modulo e o serviço:

/*
* Public API Surface of lib-name
*/

export * from './lib/http/http.module';
export * from './lib/http/auth.service';

Temos o nosso componente de login em um projeto, e o serviço de autenticação na biblioteca.

Agora precisamos fazer a comunicação entre eles.

Integrando a biblioteca na aplicação

Primeiramente, precisamos modificar o arquivo tsconfig.json da raiz do nosso workspace. A propriedade paths é responsável por mapear os diretórios, vamos modificar ela para ficar assim:

{
  // ...
  "paths": {
    "lib-name/*": [
      "projects/lib-name/src/lib/*",
      "projects/lib-name/src/lib/"
    ],
    "lib-name": [
      "dist/lib-name/*",
      "dist/lib-name"
    ]
  },
  // ...
}

O que essas configurações fazem?
A primeira (com "lib-name/*") é utilizada durante o desenvolvimento. Para que todos os imports dos arquivos da lib, utilizem a versão não compilada.
A segunda (com apenas "lib-name") é utilizada durante o tempo de build do projeto.

Agora podemos voltar no projeto app-name e modificar o arquivo login.component.ts para importar e utilizar o 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)
    );
  }
}

E o login.module.ts para importar o nosso HttpModule:

// ...
import { HttpModule } from 'lib-name/http/http.module';

@NgModule({
  // ...
  imports: [
    // ...
    HttpModule,
  ]
})
export class LoginModule { }

E quando executarmos o nosso código, veremos que o serviço está sendo chamado corretamente:

Resultado da execução

Código

O código desse tutorial está disponível em:
https://github.com/higorcavalcanti/blog-code-angular-wordspace-part1

Outras partes desse tutorial

Você terminou de ler a parte 1 da série: "Dividindo projeto angular em vários no mesmo Workspace".
Em breve haverá outras partes.


Referencias:

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


Feito com  utilizando Gatsby