Angular: Framework de desenvolvimento Web
Introdução
[editar | editar código]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]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]Componentes
[editar | editar código]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]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]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]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]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]Requisitos
[editar | editar código]Passo-a-passo
[editar | editar código]- 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:4200em seu navegador para visualizar a aplicação. A porta pode ser modificada no futuro.
Criando um componente Angular
[editar | editar código]- 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]Metadados
[editar | editar código]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]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]A exibição de atributos do componente em seu template é feito utilizando sintaxe específica {{ <atributo> }}.
Vínculo de propriedade
[editar | editar código]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]- Componente filho:
MusicControlComponent- Recebe lista de músicas que compõem a playlist como entrada, chamada de
playlistSongscom@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]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]- Componente filho:
MusicControlComponent- Define emissor de evento cujo conteúdo é a música tocando na playlist como saída, chamado de
musicChangecom@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
onMusicChangedcom<app-music-control (musicChange)="onMusicChanged($event)" />, em que$eventarmazena conteúdo emitido.
- Detecta evento de nova música tocando como método, chamado de
Vínculo bidirecional
[editar | editar código]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]- Componente filho:
MusicControlComponent- Recebe música atualmente tocando na playlist como entrada, chamada de
songPlayingcom@Input({ required: true }) songPlaying: string. - Define emissor de evento cujo conteúdo é a música tocando na playlist como saída, chamado de
songPlayingChangecom@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]Componente MusicControlComponent
[editar | editar código]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]<button (click)="onButtonClicked()">Next</button>
Componente PlaylistComponent
[editar | editar código]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]@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]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.'
]);
}
}