Saltar para o conteúdo

Angular: Framework de desenvolvimento Web

Fonte: Wikiversidade

Angular é uma framework de desenvolvimento Web desenvolvida e mantida pela Google. Ela é baseada na linguagem de programação TypeScript e é estruturada segundo a arquitetura baseada em componentes. Suas funcionalidades e ferramentas apoiam a criação de Aplicações de Página Única (SPA, single-page applications) por desenvolvedores e empresas.

Esse tutorial aborda:

  • A definição dos elementos principais de Angular;
  • Passo-a-passo da criação de um projeto simples de Angular;
  • Passo-a-passo da criação e implementação de um componente simples de Angular.

Componentes são as peças fundamentais de uma aplicação Angular. Eles agem como parte de uma página Web e podem ser reutilizados em todo o projeto.

Um componente Angular é composto por:

  • Uma classe TypeScript marcada pelo decorador @Component, responsável por controlar o estado e o comportamento do componente;
  • Um template HTML, responsável por controlar o que será adicionado ao Modelo de Documento por Objetos (DOM, documento object model);
  • Uma folha de estilo CSS, SCSS ou Sass, aplicada ao template.

Diretivas são manipuladoras de elementos do DOM e são compostas por classes TypeScript marcadas pelo decorador @Directive.

Existem três tipos de diretivas Angular:

  • Diretiva de atributo, capaz de modificar a aparência ou comportamento de elementos do DOM e outros componentes Angular;
  • Diretiva estrutural, capaz de controlar a adição, remoção ou repetição de elementos ao DOM;
  • Componente, diferenciado dos outros dois tipos por possuir um template.

Serviços são fornecedores de dados de uma aplicação Angular. Eles isolam as lógicas de recuperação de dados para que elas possam ser reutilizadas pelos componentes dependentes dos serviços que a definem.

Injeção de dependência

[editar | editar código-fonte]

Injeção de dependência é o mecanismo de gerenciamento de dependências de uma aplicação Angular.

Para injetar um serviço como dependência, são necessários dois passos:

  1. Marcar o serviço com o decorador @Injectable;
  2. Adicionar um parâmetro cujo tipo seja o serviço no método construtor do componente.

Durante a construção de uma aplicação Angular, quando é detectado que um componente depende de um serviço, o mecanismo de injeção verifica se já existe uma instância desse serviço registrada. Caso não exista, a instância única do serviço é criada e registrada no gerenciador de injeções. Caso exista ou logo após o caso anterior ser resolvido, a instância única do serviço é retornada e utilizada como parâmetro do construtor do componente analisado. A técnica de manter uma única instância de um objeto para ser reutilizada em todo o sistema é chamada de padrão de projeto Singleton.

O roteador é o controlador da navegação em uma Aplicação de Página Única Angular. Ele define a transição entre componentes que ocorre em resposta à interação do usuário com a aplicação.

Criando um projeto Angular

[editar | editar código-fonte]

Passo-a-passo

[editar | editar código-fonte]
  1. Crie um novo projeto com ng new <nome-do-projeto>. Algumas opções de configurações do projeto são dadas, mas esse tutorial considera apenas a configuração padrão do Angular v18;
  2. Mude para o diretório do seu novo projeto com cd <nome-do-projeto>;
  3. Instale as dependências internas de seu projeto com npm install. Qualquer alteração nas dependências do seu projeto precisa ser seguida desse passo, mas a instalação das dependências iniciais já está inclusa no primeiro passo;
  4. Rode seu projeto localmente em modo de desenvolvimento com ng serve. Quaisquer alterações nos arquivos serão detectadas e aplicação irá ser construída novamente para refletir as mudanças;
  5. Navegue para http://localhost:4200 em seu navegador para visualizar a aplicação. A porta pode ser modificada no futuro.

Criando um componente Angular

[editar | editar código-fonte]
  1. Crie um novo componente com ng generate component <nome-do-componente>;
  2. Mude para o diretório onde os três elementos que definem o componente acrescidos de um arquivo de testes unitários da framework Jasmine estão disponíveis com <nome-do-projeto>/src/app/<nome-do-componente>.

Implementando um componente Angular

[editar | editar código-fonte]

Metadados de um componente Angular são definidos no objeto que acompanha o decorador @Component:

  • selector: nome utilizado para se referir ao componente em templates de outros componentes, age como tag HTML;
  • imports: lista utilizada para definir as dependências do componente, que podem ser outros componentes, módulos, diretivas e pipes;
  • standalone: booleano utilizado para definir se esse componente deve ser diretamente importado em outros componentes ou se deve ser importado através de seu módulo. Esse tutorial considera apenas a configuração padrão do Angular v18, que é o primeiro caso, em que standalone é verdadeiro;
  • templateUrl: arquivo do template;
  • styleUrl: arquivo da folha de estilo.

Controlando elementos do DOM

[editar | editar código-fonte]

O controle da renderização de elementos no template do componente é feito utilizando cláusulas if/else, switch/case e for. Em versões anteriores do Angular, era necessário importar as diretivas NgIf, NgSwitch/NgSwitchCase e NgFor para possibilitar a manipulação estrutural de elementos do DOM. Em versões mais recentes, uma sintaxe específica pode ser usada no template para atingir o mesmo efeito sem nenhuma importação:

@if (<condição>) {
  ...
} @else {
  ...
}
@switch (<expressão>) {
  @case (<caso1>) {
    ...
  }

  @case (<caso2>) {
    ...
  }
}
@for (<elemento> of <lista>; track <identificador-do-elemento>) {
  ...
}

Interpolação

[editar | editar código-fonte]

A exibição de atributos do componente em seu template é feito utilizando sintaxe específica {{ <atributo> }}.

Vínculo de propriedade

[editar | editar código-fonte]

Componentes têm atributos definidos e modificados em sua classe, que representam seu estado. Caso um atributo de um componente seja marcado pelo decorador @Input, é criado um fluxo de informação unidirecional do componente pai para esse componente filho. Assim, o pai fornece um valor (estático ou por referência) para esse atributo e ele pode ser utilizado internamente no componente filho.

  • Componente filho: MusicControlComponent
    • Recebe lista de músicas que compõem a playlist como entrada, chamada de playlistSongs com @Input({ required: true }) musicasDaPlaylist: string[].
  • Componente pai: PlaylistComponent
    • Possui lista de músicas que compõem a playlist como atributo, chamado de songs.
    • Fornece lista de músicas para filho com <app-music-control [playlistSongs]="songs" />.

Vínculo de evento

[editar | editar código-fonte]

Caso um atributo de um componente seja marcado pelo decorador @Output, é criado um fluxo de informação unidirecional desse componente filho para seu componente pai. Assim, o filho emite um evento através desse atributo e ele pode ser detectado e manipulado internamente por um método do componente pai.

  • Componente filho: MusicControlComponent
    • Define emissor de evento cujo conteúdo é a música tocando na playlist como saída, chamado de musicChange com @Output() musicChange = new EventEmitter<string>().
    • Emite evento após interação do usuário com this.musicChange.emit(<musica-tocando>).
  • Componente pai: PlaylistComponent
    • Detecta evento de nova música tocando como método, chamado de onMusicChanged com <app-music-control (musicChange)="onMusicChanged($event)" />, em que $event armazena conteúdo emitido.

Vínculo bidirecional

[editar | editar código-fonte]

Caso um componente possua um atributo marcado pelo decorador @Input chamado de <atributo> e um atributo marcado pelo decorador @Output chamado de <atributo>Change, é criado um fluxo de informação bidirecional entre o componente pai e esse componente filho. Assim, o pai fornece um valor por referência para esse atributo e ele é atualizado sempre que o filho emite um evento com o novo valor ou desse atributo.

  • Componente filho: MusicControlComponent
    • Recebe música atualmente tocando na playlist como entrada, chamada de songPlaying com @Input({ required: true }) songPlaying: string.
    • Define emissor de evento cujo conteúdo é a música tocando na playlist como saída, chamado de songPlayingChange com @Output() songPlayingChange = new EventEmitter<string>().
    • Emite evento após interação do usuário com this.songPlayingChange.emit(<musica-tocando>).
  • Componente pai: PlaylistComponent
    • Possui primeira música a tocar na playlist como atributo, chamado de song.
    • Fornece e detecta evento de música com <app-music-control [(songPlayingChange)]="song" />.
    • Assim, o valor da variável passada por referência pelo pai será atualizado para o conteúdo do evento sempre que o filho o emite.

Componente MusicControlComponent

[editar | editar código-fonte]
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-music-control',
  standalone: true,
  imports: [],
  templateUrl: './music-control.component.html',
  styleUrl: './music-control.component.css'
})
export class MusicControlComponent {
  @Input({ required: true }) playlistSongs!: string[];
  @Input({ required: true }) songPlayingIdx!: number;
  @Output() songPlayingIdxChange = new EventEmitter<number>();

  public onButtonClicked(): void {
    const newSongPlayingIdx = (this.songPlayingIdx + 1) % this.playlistSongs.length;
    this.songPlayingIdxChange.emit(newSongPlayingIdx);
  }
}
<button (click)="onButtonClicked()">Next</button>

Componente PlaylistComponent

[editar | editar código-fonte]
import { Component } from '@angular/core';
import { MusicControlComponent } from '../music-control/music-control.component';
import { PlaylistService } from '../services/playlist.service';

@Component({
  selector: 'app-playlist',
  standalone: true,
  imports: [MusicControlComponent],
  templateUrl: './playlist.component.html',
  styleUrl: './playlist.component.css'
})
export class PlaylistComponent {
  public songs?: string[];
  public songIdx: number = 0;

  constructor(private _playlistService: PlaylistService) {
    this._playlistService
        .getPlaylistSongs()
        .then((playlistSongs) => this.songs = playlistSongs);
  }
}
@if (songs) {
  <h3>Currently playing: </h3>
  <span>{{ songs[songIdx] }}</span>
  <h3>Playlist:</h3>
  @for (song of songs; track $index) {
    <p>{{ song }}</p>
  }
  <app-music-control [playlistSongs]="songs" [(songPlayingIdx)]="songIdx" />
} @else {
  <p>Empty playlist.</p>
}

Serviço PlaylistService

[editar | editar código-fonte]
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class PlaylistService {
  public getPlaylistSongs(): Promise<string[]> {
    return Promise.resolve([
      'Linger - The Cranberries',
      'Under Pressure - Queen feat. David Bowie',
      'Bizarre Love Triangle - New Order',
      'Losing My Religion - R.E.M.'
    ]);
  }
}