Aprenda o básico do Angular em alguns minutos

Este texto apresenta o básico do Angular, ideal para iniciantes com alguma experiência em frontend ou em transição do backend para o frontend.

Aprenda o básico do Angular em alguns minutos
Aprenda o básico do Angular em alguns minutos

Todo desenvolvedor leva um tempo até se familiarizar com um framework. Existe uma curva de aprendizado, que pode levar mais ou menos tempo dependendo da experiência de cada um. Este texto tem o propósito de mostrar o básico do Angular para ajudar aqueles que estão começando, especialmente os que já tem alguma familiaridade com o básico de frontend e/ou estão fazendo uma migração de backend para frontend. A maior parte dos exemplos utiliza recursos disponíveis nas versões mais novas, como a 18, contudo, adicionei algumas menções à versões mais antigas.

O que é o Angular?

O Angular é um framework de desenvolvimento frontend mantido pelo Google e utilizado na criação de aplicações web dinâmicas. O Angular adota uma abordagem bem completa, fornecendo um conjunto de ferramentas integradas para desenvolvimento modular, arquitetura baseada em componentes e funcionalidades nativas para gerenciamento de estados e comunicação com APIs. Na prática, você perceberá que o framework já fornece uma série de ferramentas para ajudar nas tarefas mais comuns, como formatação de valores, requisições HTTP, injetor de dependência, roteamento de páginas, interceptors, entre outros.

Componentes (components)

A unidade básica no Angular é um componente. Um componente pode ser um botão, um formulário ou uma tela em si. A dimensão do que um componente irá compor é totalmente arbitrária.

Basicamente um componente é composto de uma classe (em TypeScript) com o decorator @Component({...}) e um template (HTML). A ideia básica é que conforme alguns valores mudem dentro da classe, o HTML seja atualizado automaticamente.

Entendendo e configurando um projeto TypeScript
Neste post, discutimos um pouco sobre o TypeScript, desde conceitos básicos até a configuração do projeto. Abordamos o tsconfig.json, ts-node, nodemon, debug no VS Code e exploramos vantagens e desvantagens da linguagem. É uma ótima leitura para quem está começando com TypeScript.

Observe no exemplo abaixo onde a variável contador tem seu valor alterado conforme o usuário clica nos botões:

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-contador',
  standalone: true,
  templateUrl: './contador.component.html',
})
export class ContadorComponent {
  contador = signal<number>(0);

  somar() {
    this.contador.update(valorAtual => valorAtual + 1);
  }

  multiplicar() {
    this.contador.update(valorAtual => valorAtual * 2);
  }
}

contador.component.ts

<div class="mt-4 mb-4">
  Contador: <strong>{{ contador() }}</strong>
</div>
<div class="d-flex gap-1">
  <button class="btn btn-primary" (click)="somar()">
    Somar 1
  </button>
  <button class="btn btn-primary" (click)="multiplicar()">
    Multiplicar por 2
  </button>
</div>

contador.component.html

Exemplo do componente acima (ContadorComponent)

"Podemos dizer que o signal "empacota" um determinado valor adicionando um comportamento reativo. A ideia é notificar que houve uma alteração em um valor para quem estiver "escutando" estas mudanças, seguindo o conceito de Producer e Consumer. Desta forma um componente pode ser "avisado" que houve uma mudança e pode atualizar o DOM, sem a necessidade de percorrer toda a árvore de componentes."

https://consolelog.com.br/introducao-angular-signals

Parametrizando os componentes com inputs e outputs

Além da capacidade de sincronização do HTML com os valores da classe, o componente pode receber e/ou enviar valores através dos inputs e outputs. Dessa forma, conseguimos parametrizar os componentes para aumentar seu reaproveitamento.

Diagrama ilustrando a composição de um componente: a classe e o template
Ilustração da composição de um componente e suas entradas (inputs) e saídas (outputs)

Observe no componente a seguir que alguns valores foram parametrizados, ou seja, serão recebidos de fora do componente. Também há um exemplo de implementação para emitir um sinal de saída, ou seja, avisar que o botão foi clicado:

import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-botoes',
  standalone: true,
  templateUrl: './botoes.component.html',
})
export class BotoesComponent {
  labelBotaoPrimario = input<string>('');
  labelBotaoSecundario = input<string>('');
  roleBotaoSecundario = input<string>('');
  mostrarBotaoSecundario = input<boolean>(true);

  aoClicarBotaoPrimario = output<void>();
  aoClicarBotaoSecundario = output<void>();

  controlarCliqueBotaoSecundario() {
    this.aoClicarBotaoSecundario.emit();
  }
}

botoes.component.ts

<div class="d-flex gap-2">
  <button
    class="btn btn-primary"
    (click)="aoClicarBotaoPrimario.emit()">
    {{ labelBotaoPrimario() }}
  </button>

  @if (mostrarBotaoSecundario()) {
    <a
      class="btn btn-link"
      [attr.role]="roleBotaoSecundario()"
      href="#"
      (click)="
        $event.preventDefault();
        controlarCliqueBotaoSecundario()
      ">
      {{ labelBotaoSecundario() }}
    </a>
  }
</div>

botao.component.html

Para utilizar esse componente em outras partes do código, ou seja, em outros componentes, basta importá-lo e depois utilizar o seletor <app-botoes>. Por exemplo, considere o componente abaixo que representa uma página:

import { Component, signal } from '@angular/core';
import {
  BotoesComponent
} from '../../componentes/botoes/botoes.component';

@Component({
  selector: 'app-exemplo-botoes',
  standalone: true,
  imports: [BotoesComponent],
  templateUrl: './exemplo-botoes.component.html',
})
export class ExemploBotoesComponent {
  mensagens = signal<string[]>([]);
  labelBtnPrimario = 'Teste';

  alterarLabelBotaoPrimario() {
    this.labelBtnPrimario = 'Teste123';
  }

  tratarCliqueBotaoPrimario() {
    this.mensagens.update(valorAtual => [
      'Botão primário clicado',
      ...valorAtual,
    ]);
  }

  tratarCliqueBotaoSecundario() {
    this.mensagens.update(valorAtual => [
      'Botão secundário clicado',
      ...valorAtual,
    ]);
  }
}

exemplo-botoes.component.ts

<h2>Utilizando o <code>app-botoes</code></h2>

<p>
  Ao utilizar o componente <code>app-botoes</code>, podemos
  passar para ele os valores dos labels e do atributo
  <code>role</code> do botão secundário:
</p>

<pre><code>
<![CDATA[
<app-botoes
  [labelBotaoPrimario]="labelBtnPrimario"
  labelBotaoSecundario="Voltar"
  roleBotaoSecundario="link"
  (aoClicarBotaoPrimario)="tratarCliqueBotaoPrimario()"
  (aoClicarBotaoSecundario)="tratarCliqueBotaoSecundario()">
</app-botoes>
]]>
</code></pre>

<app-botoes
  [labelBotaoPrimario]="labelBtnPrimario"
  labelBotaoSecundario="Voltar"
  roleBotaoSecundario="link"
  (aoClicarBotaoPrimario)="tratarCliqueBotaoPrimario()"
  (aoClicarBotaoSecundario)="
    tratarCliqueBotaoSecundario()
  "></app-botoes>

<div class="mt-4">
  <button
    class="btn btn-dark"
    (click)="alterarLabelBotaoPrimario()">
    Alterar label do botão primário
  </button>
</div>

<div class="mt-4">
  <h3>Log de cliques nos botões</h3>
  <code>
    @for (mensagem of mensagens(); track $index) {
      <div>{{ mensagem }}</div>
    }
  </code>
</div>

exemplo-botoes.component.html

Ao clicar em um dos botões, é logado na tela uma mensagem indicando qual botão foi clicado.
Exemplo do funcionamento dos inputs e outputs dos componentes

Olhando o código acima, a primeira coisa que podemos notar é o uso dos colchetes [] e dos parênteses () que são usados para fazer a ligação entre o template e a lógica do componente. Os colchetes [] são usados para passar dados do componente pai para o filho, ou seja, para definir inputs para o componente. Os parênteses () são usados para registrar eventos, ou seja, para capturar eventos do componente ou de elementos HTML e chamar um método do componente pai.

Roteamento de páginas

Quando seu projeto Angular cresce e você precisa de múltiplas páginas, o componente principal (normalmente o AppComponent) se torna o container que irá hospedar as diferentes partes da sua aplicação. Dentro do AppComponent, utilizamos a diretiva <router-outlet>. Essa diretiva indica ao Angular onde ele deve renderizar o componente correspondente à rota atual da aplicação.

💡
Para quem trabalhou com Asp.Net MVC, é similar ao conceito de Master Page.

Como funciona:

  • As rotas são definidas em um array de objetos, onde cada objeto especifica um caminho (path) e o componente que deve ser exibido quando esse caminho for acessado.
  • <router-outlet> funciona como um placeholder. Quando você navega para uma nova rota, o Angular substitui o conteúdo dentro do <router-outlet> pelo componente associado àquela rota.
Diagrama ilustrando o <router-outlet> e as rotas
Diagrama ilustrando o <router-outlet> e as rotas

Abaixo há um exemplo de código mostrando a configuração das rotas:

// *************
// app.routes.ts
// *************
import { Routes } from '@angular/router';
import {
  ExemploBotoesComponent
} from
'./paginas/exemplo-botoes/exemplo-botoes.component';
import {
  ExemploContadorComponent
} from
'./paginas/exemplo-contador/exemplo-contador.component';
import {
  ExemploServiceComponent
} from 
'./paginas/exemplo-service/exemplo-service.component';

export const routes: Routes = [
  {
    path: '',
    component: ExemploContadorComponent,
  },
  {
    path: 'botoes',
    component: ExemploBotoesComponent,
  },
  {
    path: 'contador',
    redirectTo: '',
  },
  {
    path: 'service',
    component: ExemploServiceComponent,
  },
];

// *************
// app.config.ts
// *************
import {
  ApplicationConfig,
  provideZoneChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
  ],
};

Exemplo de registro de rotas

Para navegar entre as rotas, podemos utilizar a diretiva routerLink direto nos templates ou utilizar a classe Router:

<button
  class="btn btn-link"
  [routerLink]="['/contador']">
  Contador
</button>
<button
  class="btn btn-link"
  [routerLink]="['/botoes']">
  Botões
</button>

Exemplo de como navegar para outras rotas através do routerLink

import { Component, inject } from '@angular/core';
import {
  Router,
  RouterModule,
  RouterOutlet,
} from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, RouterModule],
  templateUrl: './app.component.html',
})
export class AppComponent {
  // Podemos injetar assim:
  router = inject(Router);

  // ou podemos utilizar o construtor:
  // constructor(private router: Router) {
  // }

  irParaPaginaBotoes() {
    this.router.navigate(['/botoes']);
  }
}

Exemplo de como navegador para outras rotas utilizando a classe Router

Navegador mostrando a troca de conteúdo conforme o usuário clica nos links
Exemplo de roteamento da aplicação

Importante: para ambos os casos é necessário importar o RouterModule na declaração imports do componente ou módulo.

Abaixo há alguns links de posts mais antigos envolvendo o tema rotas do Angular:

Como corrigir o erro 404 no Angular ao efetuar o refresh ou acessar rotas diretamente do navegador
O erro 404 no Angular surge quando o servidor web não está configurado para lidar com o roteamento client-side do Angular. Aprenda a corrigir esse problema no Nginx e Apache
Como proteger suas rotas - Angular Guard CanActivate
Exemplo de como proteger suas rotas com a implementação do CanActivate (guard) do Angular.
Defina um título para cada rota - Angular Route Title
Veja como utilizar o Title, Router e ActivatedRoute para mudar o título da sua aplicação de acordo com a rota que está ativa.

Templates HTML

Assim como outros motores de renderização, como Razor (do C#), EJS (do Node.js) ou Thymeleaf (do Java), o Angular fornece uma sintaxe para controle de fluxo no template, ou seja, podemos utilizar algumas palavras reservadas para criar lógicas dentro do template, como um "if" ou um "loop for".

import { CommonModule } from '@angular/common';
import { Component, signal } from '@angular/core';

@Component({
  standalone: true,
  imports: [CommonModule],
  templateUrl: './exemplo-controle-fluxo.component.html',
})
export class ExemploControleFluxoComponent {
  msgFixa = 'Mensagem fixa';
  msgVariavel = signal<string>('Mensagem variável');
  contador = signal<number>(0);
  pessoas = [
    { id: 1, nome: 'Ana', idade: 25 },
    { id: 2, nome: 'Bruno', idade: 30 },
    { id: 3, nome: 'Carla', idade: 28 },
  ];

  incrementar() {
    this.contador.update(valorAtual => valorAtual + 1);
  }
}

exemplo-template.component.ts

<div>{{ msgFixa }}</div>
<div>{{ msgVariavel() }}</div>

@if (contador() % 2 === 0) {
  <div>Contador Par</div>
} @else {
  <div>Contador Ímpar</div>
}

@for (pessoa of pessoas; track pessoa.id) {
  <div>{{ pessoa.nome }} {{ pessoa.idade }}</div>
}

<div>
  <button (click)="incrementar()">
    Incrementar Contador
  </button>
</div>

<hr>

<!--
  A sintaxe em versões mais antigas é um pouco
  diferente, inclusive, comentei sobre a nova
  sintaxe neste texto:

  https://consolelog.com.br/introducao-ao-angular-17
-->
<div *ngIf="contador() % 2 === 0">Contador Par</div>
<div *ngIf="contador() % 2 !== 0">Contador Ímpar</div>

<div *ngFor="let pessoa of pessoas">
  {{ pessoa.nome }} {{ pessoa.idade }}
</div>

exemplo-template.component.html

Serviços (services)

Responsáveis por encapsular a lógica de negócio, as classes da camada de serviço (service) podem ser reutilizadas em outras partes do projeto. Para isto, são registradas no injetor de dependência do Angular através do decorator @Injectable.

💡
O injetor de dependência do Angular permite a criação de classes que podem ser injetadas em outras partes do código, promovendo a modularidade e a testabilidade.

Para quem está familiarizado com o SpringBoot, o @Injectable do Angular funciona de forma similar ao @Service. O atributo providedIn dentro do @Injectable define o escopo de fornecimento do serviço, indicando onde o Angular deve criar e fornecer uma instância desse serviço. Por exemplo, providedIn: 'root' significa que o serviço estará disponível em toda a aplicação.

Services e injeção de dependência no Angular
Neste post, vamos explorar o que é a Injeção de Dependências no Angular, como funciona e por que ele é crucial para a criação de aplicações modulares e testáveis.

Texto falando sobre a injeção de dependências no Angular

Considere o exemplo abaixo onde a classe CarrinhoService é registrada de forma global, ou seja, haverá uma única instância dessa classe para toda a aplicação. Dessa forma, podemos consumir informações dessa instância em vários componentes, criando assim um canal de comunicação entre os componentes para compartilhamento de dados, por exemplo.

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class CarrinhoService {
  itens = new BehaviorSubject<string[]>([]);

  obterDados(): Observable<string[]> {
    return this.itens.asObservable();
  }

  atualizarDados(novoValor: string): void {
    const valorAtual = this.itens.getValue();
    valorAtual.push(novoValor);

    this.itens.next([...valorAtual]);
  }
}

carrinho.service.ts

Utilizando o CarrinhoService no componente ExemploServiceComponent e no CarrinhoComponent:

import { Component } from '@angular/core';
import {
  CarrinhoService
} from '../../services/carrinho.service';
import {
CarrinhoComponent
} from '../../componentes/carrinho/carrinho.component';

@Component({
  selector: 'app-exemplo-service',
  standalone: true,
  imports: [CarrinhoComponent],
  templateUrl: './exemplo-service.component.html',
})
export class ExemploServiceComponent {

  // Podemos injetar utilizando o `inject`...
  // compartilharDadosService = inject(CarrinhoService);

  // ...ou declarar como parâmetro no construtor:
  constructor(
    private compartilharDadosService: CarrinhoService
  ) {}

  adicionar(item: string) {
    this.compartilharDadosService.atualizarDados(item);
  }
}

exemplo-service.component.ts

<p>Exemplo do <code>Injectable</code></p>

<div class="d-flex gap-4">
  <div class="flex-grow-1">
    <div class="d-flex flex-column gap-4">
      <div class="d-flex border p-2">
        <div class="flex-grow-1">Item 1</div>
        <button
          class="btn btn-primary"
          (click)="adicionar('Item 1')">
          Adicionar
        </button>
      </div>
      <div class="d-flex border p-2">
        <div class="flex-grow-1">Item 2</div>
        <button
          class="btn btn-primary"
          (click)="adicionar('Item 2')">
          Adicionar
        </button>
      </div>
      <div class="d-flex border p-2">
        <div class="flex-grow-1">Item 3</div>
        <button
          class="btn btn-primary"
          (click)="adicionar('Item 3')">
          Adicionar
        </button>
      </div>
    </div>
  </div>
  <div class="bg-light">
    <app-carrinho></app-carrinho>
  </div>
</div>

exemplo-service.component.html

import { Component } from '@angular/core';
import {
  CarrinhoService
} from '../../services/carrinho.service';

@Component({
  selector: 'app-carrinho',
  standalone: true,
  templateUrl: './carrinho.component.html',
})
export class CarrinhoComponent {
  itensDoCarrinho: string[] = [];

  constructor(carrinhoService: CarrinhoService) {
    carrinhoService.obterDados().subscribe(itens => {
      this.itensDoCarrinho = itens;
    });
  }
}

carrinho.component.ts

<div class="p-4">
  <strong>Carrinho:</strong>
</div>

<table>
  @for (item of itensDoCarrinho; track $index) {
    <tr>
      <td>{{ item }}</td>
    </tr>
  }
</table>

carrinho.component.html

Uma lista de 3 itens com um botão "adicionar" em cada. Ao clicar no botão, o item é adicionado no carrinho
Exemplo de uso de uma service para compartilhar dados entre componentes

Módulos (NgModule)

Os módulos do Angular podem ser pensados como um agrupador. Nele, podemos declarar componentes, diretivas, pipes e serviços. Esse modelo ajuda na organização e modularização da aplicação, pois podemos organizar um conjunto de recursos dentro de módulos. Também é possível exportar recursos declarados em um módulo para serem reaproveitados em outros. Seria uma forma similar a um JAR para quem programa em Java ou DLL para quem trabalha com C#.

Em versões anteriores ao Angular 14, essa organização ao redor dos módulos era obrigatória, e cada componente precisava estar associado a um módulo. A partir do Angular 14, foi introduzido a opção standalone, que permite que os componentes, pipes e diretivas não necessitem ser declarados em um módulo.

Entendendo o pure e standalone do Angular Pipe
Veja as diferenças de valores nos parâmetros pure e standalone de um Angular Pipe. Aprenda a usá-los para melhorar o desempenho e a reutilização.

Um exemplo bem comum sobre o uso de módulos no Angular, é o uso de um módulo compartilhado, normalmente chamado de SharedModule. O objetivo é declarar (declarations) e exportar (exports) componentes comuns a todo projeto. Desta forma basta importar este módulo para ter acesso a todos os recursos exportados. Exemplo retirado da documentação oficial do Angular:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CustomerComponent } from './customer.component';
import { NewItemDirective } from './new-item.directive';
import { OrdersPipe } from './orders.pipe';
@NgModule({
  imports: [CommonModule],
  declarations: [
    CustomerComponent,
    NewItemDirective,
    OrdersPipe
  ],
  exports: [
    CustomerComponent,
    NewItemDirective,
    OrdersPipe,
    CommonModule,
    FormsModule
  ],
})
export class SharedModule { }

Diretivas (directives)

As diretivas são como atributos de um elemento HTML que o Angular identifica e aplica uma determinada lógica. Por exemplo, considere a diretiva abaixo que quando usada, modifica o visual do elemento HTML nos eventos mouseover e mouseout:

import {
  Component,
  Directive,
  ElementRef,
  OnInit,
  Renderer2,
} from '@angular/core';

@Directive({
  standalone: true,
  selector: '[aplicarBordaMouseOver]',
})
export class AplicarBordaNoMouseOver implements OnInit {
  constructor(
    private elemento: ElementRef<HTMLElement>,
    private renderer: Renderer2
  ) {}

  ngOnInit(): void {
    const elementoHtml = this.elemento.nativeElement;
    const defaultBorder = elementoHtml.style.border;

    this.renderer.listen(elementoHtml, 'mouseover', () => {
      elementoHtml.style.border = '4px solid #ff0000';
    });

    this.renderer.listen(elementoHtml, 'mouseout', () => {
      elementoHtml.style.border = defaultBorder;
    });
  }
}

// *********************************************************

@Component({
  standalone: true,
  imports: [AplicarBordaNoMouseOver],
  templateUrl: './exemplo-diretiva.component.html',
})
export class ExemploDiretivaComponent {}
<h2>Exemplo do uso de diretivas</h2>

<div aplicarBordaMouseOver>
  Lorem ipsum dolor sit amet consectetur adipisicing elit.
  Vitae, aliquid.
</div>
<div aplicarBordaMouseOver>Lorem ipsum dolor sit amet.</div>
<div aplicarBordaMouseOver>
  Lorem ipsum dolor sit amet consectetur adipisicing elit.
  Impedit eum earum recusandae voluptates consectetur.
  Repellendus.
</div>

Template do ExemploDiretivaComponent

Resultado do código acima:

Diretivas são como "receitas" que você dá ao Angular para que ele modifique o comportamento ou a aparência de um elemento. Elas são como pequenas instruções que você insere no seu HTML para dizer ao Angular o que fazer com aquele elemento.

💡
Existem várias diretivas prontas para uso. Vale dar uma olhada na documentação oficial para conhecer melhor:
https://angular.dev/guide/directives

Pipes

Os pipes (|) ajudam na formatação e transformação de dados no Angular. Com ele é possível separar responsabilidades. Por exemplo, considere o código abaixo que alimenta a variável data e depois aplica uma formatação no dataFormatada:

💡
Podemos declarar o conteúdo do template de um componente inline, ou seja, direto no atributo template, conforme abaixo. A alternativa é utilizar um arquivo externo, neste caso basta indicar o nome do arquivo no atributo templateUrl.
@Compoment({
  standalone: true,
  imports: [CommonModule],
  template: `Data: {{ dataFormatada }}`,
})
export class TestComponent {
  dataFormatada: string = '';
  data: Date = new Date();

  obterValores() {
    this.data = new Date('2024-01-01T12:00:00.000Z');

    const day = String(data.getDate()).padStart(2, '0');
    const month = String(data.getMonth() + 1).padStart(2, '0');
    const year = data.getFullYear();

    this.dataFormatada = `${day}/${month}/${year}`;
  }
}

Utilizando um pipe é possível simplificar o código acima, além de separar as responsabilidades de formatação de dados:

@Compoment({
  standalone: true,
  imports: [CommonModule],
  template: `Data: {{ data | date : 'dd/MM/yyyy }}`,
})
export class TestComponent {
  data: Date = new Date();

  obterValores() {
    this.data = new Date('2024-01-01T12:00:00.000Z');
  }
}

Via de regra, sempre que precisar formatar ou transformar um dado para exibição, sempre opte por um pipe.

💡
O Angular disponibiliza uma série de pipes nativos: https://angular.dev/api?query=pipe#angular_common

Abaixo deixo algumas referências de textos publicados por aqui envolvendo o Angular Pipe:

Entendendo o pure e standalone do Angular Pipe
Veja as diferenças de valores nos parâmetros pure e standalone de um Angular Pipe. Aprenda a usá-los para melhorar o desempenho e a reutilização.
Formatar data - DatePipe - Angular
Formatação de data com certeza é uma das tarefas mais comuns para quem trabalha com frontend. Veja como fazer isto de uma forma bem simples com o DatePipe.
Como criar um pipe para formatar CPF no Angular 2+ - CpfPipe
Com a utilização de um pipe podemos transformar e/ou formatar informações direto no template. Tarefas comuns como formatar moeda, data, cpf, entre outros, podem ser efetuadas através do PipeTransform. Veja neste artigo como formatar um CPF utilizando este recurso - CpfPipe
Filtrar dados na tabela - Angular
Veja como filtrar os dados de uma tabela de forma dinâmica através da utilização de um PipeTransform e o operador debounceTime do RxJS.
Formatar valor (moeda) em Angular 2+ utilizando CurrencyPipe
Como formatar valores (moeda) utilizando o CurrencyPipe do Angular.

Considerações

O Angular vai muito além do que foi comentado neste texto. Contudo, o objetivo do texto é mostrar o básico do framework de uma forma bem rápida e prática. Abaixo há uma lista dos itens abordados de uma forma mais resumida:

  • Componentes: São a base da construção de interfaces no Angular, encapsulando tanto a lógica quanto a visualização. São marcados com @Component() e podem representar desde páginas inteiras até elementos simples como botões.
  • Injectables: Registram classes no injetor de dependências do Angular, permitindo que sejam injetadas em outros componentes, diretivas e serviços. Similar ao @Service do Spring Boot.
  • Diretivas: Adicionam comportamentos e modificações a elementos HTML. Existem diretivas estruturais (como *ngIf*ngFor) que alteram o DOM e diretivas de atributos (comongClassngStyle) que modificam propriedades de elementos.
  • Pipes: Transformam dados para exibição. Exemplo: formatar datas, converter maiúsculas para minúsculas, etc.
  • Módulos: Agrupam componentes, diretivas, pipes e serviços, definindo o contexto para injeção de dependências. Podemos fazer uma analogia a um namespace do C# ou package do Java, onde agrupamos um determinado conjunto de recurso que podem ser expostos ao mundo externo de acordo com as configurações desejadas.
Angular: atribuindo classes CSS dinamicamente com ngClass
Descubra como a diretiva ngClass simplifica a manipulação de classes CSS de forma intuitiva e eficiente. Neste post, você aprenderá a adicionar e remover classes CSS condicionalmente. Além disso, mostraremos como a ngClass contribui para um código mais limpo e manutenível.

Texto falando sobre o uso da diretiva ngClass

Neste link há uma sugestão de roadmap para aprendizado, talvez possa ajudar a organizar seus estudos.

No link abaixo, deixo a listagem de todo o conteúdo publicado por aqui a respeito do Angular:

Angular: veja dicas, exemplos, novidades e dúvidas sobre Angular
Explore uma variedade de exemplos e dicas sobre o framework frontend Angular. Aprenda sobre componentes, Angular Material e uma série de outros temas.