Construindo um progress bar - Angular

A barra de progresso é um componente visual que ajuda o usuário a entender o status de processamento de alguma atividade. Neste texto vamos abordar a construção de uma barra de progresso em Angular.

Construindo um progress bar - Angular
Construindo um progress bar - Angular

A barra de progresso é um dos componentes mais presentes no frontend. Em vários fluxos como upload, download, processamento, etapas de formulários, entre outros, há a possibilidade de incluir uma barra de progresso.

O objetivo deste texto é mostrar como podemos criar uma simples barra de progresso em Angular.

Ideia para construção da barra de progresso

Para construir o componente, vamos definir 3 parâmetros de entrada. Um pouco mais abaixo há uma imagem com um esboço do visual do componente.

Parâmetros de entrada:

  1. label1 - Label que aparecerá no canto superior esquerdo
  2. label2 - Label que aparecerá no canto superior direito
  3. progresso - Número de 0 a 100 que representa a porcentagem da barra e também será exibido no canto inferior direito

Esboço do componente:

Esboço do componente barra de progresso
Esboço do componente barra de progresso

Codificando a barra de progresso

O componente será composto por 4 arquivos:

  1. progresso.component.ts
  2. progresso.component.html
  3. progresso.component.css
  4. progresso.module.ts
import { Component, Input } from '@angular/core';

@Component({
    selector: 'app-progresso',
    templateUrl: 'progresso.component.html',
    styleUrls: ['progresso.component.css'],
})
export class ProgressoComponent {
    @Input() label1: string = 'Label 1';
    @Input() label2: string = 'Label 2';
    @Input() progresso: number = 10;
}
progresso.component.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ProgressoComponent } from './progresso.component';

@NgModule({
  declarations: [ProgressoComponent],
  imports: [CommonModule],
  exports: [ProgressoComponent],
})
export class ProgressoModule {}
progresso.module.ts
<div>
    <div>{{label1}}</div>
    <div>{{label2}}</div>
</div>
<div>
    <div></div>
    <!-- -------------------------------------
         Este div (abaixo) terá a largura em %
         de acordo com o parâmetro progresso
    -------------------------------------- -->
    <div [style.width.%]="progresso"></div>
</div>
<div>
    <div></div>
    <div>{{(progresso / 100) | percent}}</div>
</div>
progresso.component.html

O percent pipe já foi abordado em um outro artigo: https://consolelog.com.br/input-porcentagem-angular/


Inicialmente vamos deixar o arquivo progresso.component.css.

Repare que nosso template (progresso.component.html)  não tem nenhum CSS, então sua renderização ficará da seguinte forma:

Renderização da barra de progresso
Renderização da barra de progresso

Dá para notar que está bem diferente do que queremos, mas aos poucos vamos chegar lá. Vamos começar trabalhando na parte superior do componente, onde ficarão os dois labels. Veja na imagem abaixo um esquema visual de como iremos organizar o HTML para que os labels fiquem no canto superior esquerdo e canto superior direito:

Esquema visual do HTML utilizado no template do componente
Esquema visual da organização do HTML

Uma das formas de chegarmos no resultado desejado é através do flexbox.

"O Flexible Box Module, geralmente chamado de flexbox, foi projetado tanto como um modelo de layout unidimensional quanto como um método capaz de organizar espacialmente os elementos em uma interface, além de possuir capacidades avançadas de alinhamento. Este artigo fornece um resumo das principais funcionalidades do flexbox, as quais exploraremos com mais detalhes no restante deste guia."

fonte: https://developer.mozilla.org/pt-BR/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox

Então vamos criar 2 classes CSS no arquivo progresso.component.css:

.d-flex {
    display: flex;
}

.flex-grow-1 {
    flex-grow: 1;
}
progresso.component.css

A classe d-flex define um container flex. Consequentemente os elementos contidos dentro dele serão posicionados um ao lado do outro em uma disposição de colunas - este é o comportamento padrão, mas existem outras opções.

A classe flex-grow-1 indica que o elemento ocupará todo o espaço disponível em seu eixo, que no nosso caso é o eixo X.

Agora vamos aplicar as classes no template (progresso.component.html):

<div class="d-flex">
    <div class="flex-grow-1">{{label1}}</div>
    <div>{{label2}}</div>
</div>
<div>
    <div></div>
    <div
         [style.width.%]="progresso"
    ></div>
</div>
<div>
    <div></div>
    <div>{{(progresso / 100) | percent}}</div>
</div>
progresso.component.html

Com estas alterações podemos ver que chegamos no resultado esperado na parte superior do componente:

Renderização do componente barra de progresso
Renderização do componente barra de progresso

Podemos aplicar esta mesma lógica na parte inferior, abaixo da barra de progresso:

<div class="d-flex">
    <div class="flex-grow-1">{{label1}}</div>
    <div>{{label2}}</div>
</div>
<div>
    <div></div>
    <div
         [style.width.%]="progresso"
    ></div>
</div>
<div class="d-flex">
    <div class="flex-grow-1"></div>
    <div>{{(progresso / 100) | percent}}</div>
</div>
progresso.component.html

Resultado:

Renderização do componente barra de progresso
Renderização do componente barra de progresso

Agora precisamos ajustar o meio, a barra de progresso propriamente dita. Vamos utilizar o flexbox, porém vamos dispor os elementos filhos na vertical, ou seja, um <div> em baixo do outro. Para fazer isto é bem simples, basta adicionar o estilo flex-direction: column ao container flex.

No primeiro <div> vamos criar um background cinza para a barra de progresso. No segundo <div>, que fica em baixo, vamos criar o indicador de progresso.

Para que o indicador de progresso fique em cima do background da barra, vamos deslocá-lo utilizando uma margem negativa.

Abaixo há um esquema visual:

Trazendo esta ideia para o código:

.d-flex {
    display: flex;
}

.flex-grow-1 {
    flex-grow: 1;
}

.flex-column {
    flex-direction: column;
}

.barra-background, .barra-progresso {
    border-radius: 8px;
}

.barra-background {
    background: #ccc;
    height: 4px;
    width: 100%;
}

.barra-progresso {
    background: green;
    height: 8px;
    margin-top: -6px;
    transition: all .5s;
    width: 44%;
}
progresso.component.css
<div class="d-flex">
    <div class="flex-grow-1">{{label1}}</div>
    <div>{{label2}}</div>
</div>
<div class="d-flex flex-column">
    <div class="barra-background"></div>
    <div class="barra-progresso"
         [style.width.%]="progresso"
    ></div>
</div>
<div class="d-flex">
    <div class="flex-grow-1"></div>
    <div>{{(progresso / 100) | percent}}</div>
</div>
progresso.component.html

Agora o componente já está com a "cara" que esboçamos no início deste texto:

Renderização do componente barra de progresso

Melhorando o layout

Para melhorar o layout vamos aplicar um pouco de "perfumaria" como margens, estilo de fontes e efeitos de transição:

<div class="d-flex margin-bottom-1">
    <div class="flex-grow-1 font-size-small">{{label1}}</div>
    <div class="font-size-small">{{label2}}</div>
</div>
<div class="d-flex flex-column margin-bottom-1">
    <div class="barra-background"></div>
    <div class="barra-progresso"
         [style.width.%]="progresso"
    ></div>
</div>
<div class="d-flex">
    <div class="flex-grow-1"></div>
    <div class="font-bold font-size-small">
        {{(progresso / 100) | percent}}
    </div>
</div>
progresso.component.html
.d-flex {
    display: flex;
}

.flex-grow-1 {
    flex-grow: 1;
}

.flex-column {
    flex-direction: column;
}

.barra-background, .barra-progresso {
    border-radius: 8px;
}

.barra-background {
    background: #ccc;
    height: 4px;
    width: 100%;
}

.barra-progresso {
    background: green;
    height: 8px;
    margin-top: -6px;
    transition: all .5s;
    width: 44%;
}

.font-regular {
    font-family: 'Roboto';
    font-weight: 300;
}

.font-bold {
    font-family: 'Roboto';
    font-weight: 500;
}

.font-size-small {
    font-family: 'Roboto';
    font-size: .8em;
}

.align-items-center {
    align-items: center;
}


.margin-bottom-1 {
    margin-bottom: 8px;
}
progresso.component.css

Resultado final:

Resultado final - Componente barra de progresso
Resultado final - Componente barra de progresso

Considerações

Este componente é razoavelmente simples de se construir. Se você não tem tanta experiência com flexbox, vale gastar um pouco de tempo estudando, com certeza vai te ajudar em vários momentos.

Vale lembrar que há outras formas de se chegar neste resultado. Espero ter ajudado e deixo abaixo alguns links interessantes: