Ir para o conteúdo

Angular: Framework de desenvolvimento Web

De Wikiversidade

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.

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:

  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]

Requisitos

[editar | editar código]

Passo-a-passo

[editar | editar código]
  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]
  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]

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.

  • 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]

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]

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]
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]
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]
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.'
    ]);
  }
}