Angular: Framework de desenvolvimento Web
Introdução
[editar | editar código-fonte]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.
Objetivo
[editar | editar código-fonte]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.
Elementos
[editar | editar código-fonte]Componentes
[editar | editar código-fonte]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
[editar | editar código-fonte]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
[editar | editar código-fonte]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:
- Marcar o serviço com o decorador
@Injectable
; - 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.
Roteador
[editar | editar código-fonte]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]Requisitos
[editar | editar código-fonte]Passo-a-passo
[editar | editar código-fonte]- 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; - Mude para o diretório do seu novo projeto com
cd <nome-do-projeto>
; - 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; - 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; - 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]- Crie um novo componente com
ng generate component <nome-do-componente>
; - 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
[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.
Exemplo
[editar | editar código-fonte]- Componente filho:
MusicControlComponent
- Recebe lista de músicas que compõem a playlist como entrada, chamada de
playlistSongs
com@Input({ required: true }) musicasDaPlaylist: string[]
.
- Recebe lista de músicas que compõem a playlist como entrada, chamada de
- 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" />
.
- Possui lista de músicas que compõem a playlist como atributo, chamado de
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.
Exemplo
[editar | editar código-fonte]- 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>)
.
- Define emissor de evento cujo conteúdo é a música tocando na playlist como saída, chamado de
- 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.
- Detecta evento de nova música tocando como método, chamado de
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.
Exemplo
[editar | editar código-fonte]- 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>)
.
- Recebe música atualmente tocando na playlist como entrada, chamada de
- 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.
- Possui primeira música a tocar na playlist como atributo, chamado de
Exemplo
[editar | editar código-fonte]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);
}
}
Template
[editar | editar código-fonte]<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);
}
}
Template
[editar | editar código-fonte]@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.'
]);
}
}