Галоўная / Артыкулы / Компаненты Angular ад з нуля: селектары, шаблоны, вводы, выходы

Компаненты Angular ад з нуля: селектары, шаблоны, вводы, выходы

Практычны ўражынкі сучасных самастоятельных компанентаў Angular: структура файлоў, декоратары, селектары, шаблоны, стылі з абмежэнням дасягамоў, а таксама перадача даных межу бацькім і дзецячым компанентамі за дапамойкай input() і output().

2504 слоў

Калі проект Angular ўжо створаны і працюе, наступны вопыт — як сам экран будуецца. Адказ — компаненты: маленькія, самостатныя елементы, кожны з якіх адпаведае за частку інтэрфейсу. У гэтым кялікасе паспрабована адпаведзіць, з чаго складаецца компанент, як самастатныя компаненты змінюють ситуацыю па пораўнанні з старэйшым кодам, базаваным на NgModule, і як родны та дзецінскі компаненты адмаўляюцца межу інфармацыяй та запускамі супэрвізора за дапамогою сучасных API input() та output(). До канца вы должны змагчыць стварэнне компаненту, падключыць яго да сторунки та перадаваць інфармацыю ў обох направленнях.

Якщо вам яшчэ трэба проект для эксперыментавання, інструкцыя па стварэнні сучаснага проекту Angular 22 охопляе настройкі та структуру папак, якія прымутліваюцца ў гэтым артыкуле.

Чаму Angular дзеліе UI на компаненты

Уявіце звычную веб-сторанку: паліту навігацыі ў верхней частцы, боковую панель, контент продукту ў середзіне і футэр пад яму. Вы моглі б разам з усім этым маркапом, стайлінгам і поведенням паклаць усё гэта ў адны вялічезны файл, але ён бы шыбкаа стаў важкім для чытання і щэ вялікай меры — для змены. Angular заохочвае вас замест таго раздзеліць сторанку на часткі, кожная з якіх виконвае адну задачу.

Angular Application
│
├── Navbar Component
├── Sidebar Component
├── Product Component
├── Login Component
└── Footer Component

Компанента зазвычай складаецца з чатырох элементаў: класу TypeScript, які выконвае функцыі стану і поведзення, шаблона HTML, які описвае структуру элементаў, стыляў для яго візуальнага выгляду, а таксама метаданых, якія паведамляюць Angular, як з ім працаваць. Паколькі кожны элемент мае чыста вялічыну адпаведальнасці, можна аналізаваць navbar без увагі да footer, перадаўаць той самы карточкі продукту ў разных месцах і тэставаць часткі UI адсаклена. Форма для заходжання, панель прынтараў або віджет дашборда — усе гэта ўзгодныя прыклады.

Стандальнія компаненты ўважаюцца стандартам

Большынства нарадчыкаў па Angular, ў чым спецыяльна старэйшых, складаюць компаненты ўнутрь NgModule, які адзначае компаненты і пераказвае, чаму яны залежнаць. Савэчасны Angular адступіў ад такога падходу. Компаненты тепер за замовчаннем ёсць самостойныя, таму компанент можа існаваць сам по сабе без рэўістрацыі ў модуле. Мінімальны компанент выглядае так:

import { Component } from '@angular/core';
@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})
export class WelcomeComponent {
}

Можна было б спадзявацца побачыць флажок, які адзначае компанент як самостойны, падобна да наступнай лініі, але гэта больш не ўскладненне:

standalone: true

Яго выключэнне дае той самай наследкі, калі ўвесь час выкалікаецца стандартная працэздатнась. Калі самастоятельны компонент патрэбуе іншага компонента, дырективы або каналу, ён зазначае гэтую залежнась у масіве imports сваёй сабеўтароўкі @Component. Працэздатнась заключаецца ў тым, што можна ачысці одні файл і пачаць з’ясоўваць, на чым самэлькі паводзіцца той компонент, замест таго, каб шукаць у вядомасці модуля ў іншай частыне проекта.

Як компонент размешчаецца на дыску

Тыповы компонент знаходзіцца у сваёй сабеўтароўцы, дзе пасярод сабе знаходзяцца тры файлы:

welcome/
│
├── welcome.ts
├── welcome.html
└── welcome.css

Файл .ts мае логіку, файл .html — маркап, а файл .css — стылі. Чаму можа мець кожны з іх у простым компоненте для прывітання. Спачатку клас, які выкладзе атрыбут title у шаблон:

import { Component } from '@angular/core';
@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})
export class WelcomeComponent {
  title = 'Welcome to Angular 22';
}

Шаблон чытае гэтую атрыбутацыю за дапоўнюючымі кавычкамі:

<h1>{{ title }}</h1>
<p>
  This is my first Angular component.
</p>

А стайл-шыт ўражае толькі элементы ў гэтым шаблоне:

h1 {
  color: #dd0031;
}
p {
  font-size: 18px;
}

Декоратар @Component() являе сабою з’ёднанне. Ён паказывае Angular на шаблон і стайл-шыт, такім чынам трое файлаў працуюць як адна ейка, як паказана ў наступным атласе:

welcome.ts
    │
    ├── Logic
    │
    ├── welcome.html
    │      ↓
    │    UI
    │
    └── welcome.css
           ↓
         Style

Кераванне стылем Angular рэкамендуе трыважліва звязаныя файлы, такія як гэтыя, размешчаць у аднам каталогу, і самэ гэта робіць такая структура. Калі компанента расте, вы точна ведаеце, дзе знаходзяцца яе маркап, стылі і логіка.

Што декоратар @Component() паведамляе Angular

Декоратар прыўязвае мета-даныя да класу. Гэтыя мета-даныя дапамагають Angular з’ясавіць, для чаго патрэбен клас і як яго адобразіць:

@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})

Тут большасць работы выканае трое атрыбутаў:

  • selector паказвае назыв кантэксту, які вы вжываеце для размешчання компонента ў іншых шаблонах.
  • templateUrl паказвае файл HTML з маркапом компонента.
  • styleUrl паказвае файл стылю компонента.

Існуюць ўсё большыя опцыі, такія як imports для запоўнення іншымі компонентамі або функцыямі Angular, а таксама альтэрнатывы templateUrl і styleUrl у вигляді айнлайн-значэнняў, пра якія пойдзе мова пазней у гэтым артыкуле. Няма прычыны вучыцца ўсё гэта з самага пачатку; вы прывыкнете да іх праз стварэнне большых колькасці компонентаў.

Селектары ператвараюць клас у спецыяльны элемент

Селектар — гэта ідэнтыфікатор, які Angular шукае ў шаблонах. У гэй канфігурацыі ён задаўся як app-welcome:

@Component({
  selector: 'app-welcome',
  ...
})

Такім чынам, сама значэнне селектара ёсць проста:

app-welcome

Там, дзе вы хачаце, каб компанента з’явілася, вы пісваеце ега назву так, як гэта HTML-таг:

<app-welcome></app-welcome>

Калі Angular зустрэчае <app-welcome> пад выкананнем шаблона, ён стварае экземпляр адпаведной компаненты і выканае ўсі её шаблоны внутры гэтага элемента. Элемент, які падходзіць пад селектар, называецца хост-элементам компаненты, і ён застаецца ў DOM; гэта мае значэнне пазней, калі вы хачаце стылізаваць або дадаць певнае поведэнне всій компаненты цалкам.

Angular CLI стварае селектары з прыфіксам, настроеным для вашай аплікацыі, зазвычай app-, хоць вы можете выбраць іншы селектар пад час стварэння компонента. Прыфікс мае не толькі дэзорыянтны характер: назвы спецыяльных элементаў должны міткавацца гіпенам, а прыфікс, спецыяльны для проекта, запобегае зіштовхненню вашых компонентаў з натывнымі элементамі чы рэшткамі з бібліятэкаў трохцярных разработчыкаў.

Шаблоны спаяваюць маркап з дадзеннямі компонента

Шаблон параджуе тое, што на самай простай адзынцы ёсць звычны HTML:

<h1>Welcome!</h1>
<p>We are learning Angular components.</p>

Тое, што робіць шаблоны Angular моцнейшымі за статычны HTML, — гэта ўмеласць чытаць дадзеныя і вызываць функціі, заданыя ў класе компонента. Падазроўваючы, што клас адзначае якую-небудзь власнасць:

name = 'Mubbassir';

Шаблон можа пасля таго адобразіць яе за дапамогою інтэрполяцыі:

<h1>Hello {{ name }}!</h1>

Angular падтрымлее сінхроннасць адразу выкананага кантэнту з данымі: калі зменяецца значэнне, прыязначанае да элемента, стораніца апдэйтуецца без неабяжнага рэгулювання DOM. Гэты звязак между класам на TypeScript і яго шаблонам ёсць аднойчынай базой для всіх іншых функцый Angular, ад зьвяртання з дзействаў да форм.

Стылі компанентаў за замовчаннем застаюцца абмежаныя

Кожны компанент можа маты своія сабскайны стылі:

.card {
  padding: 20px;
  border-radius: 10px;
}
h2 {
  color: #dd0031;
}

За замовчаннем Angular прыменяе емуляванае інкапсулаванне відображэння да гэтых правілаў. У час будовы ён перапісвае селектары і дадае створаныя атрыбуты элементам компанента, тады правілу на кшталт h2 { ... } паспадзяюцца толькі элементы h2 у шаблоне гэтага компанента, а не всі загаловкі ў прыемле. У прыемле з сотнямі компанентаў такая захоць не дазволяе незначным зменам стыліў у аднам месцы пашкодзіць выгляд іншага компанента.

Капсуляванне не выключае можлівасць спяльнага стылю. Правіла, які павінны дапраўляцца всюды, такія як тыпаграфіка, настройкі перзначэння або інструменты для макетавання, патрабуюцься ў глобальным файле стылю праекта, тады як файлы компанентав утримваюць стылі, спецыфічныя для таго компаненту.

Стварэнне компаненту за дапамою Angular CLI

Стварэнне файлаў вручную таксама можліва, але CLI робіць гэта быстрей і з аднаковым называнням. З корневага каштоўка проекту запускайце:

ng generate component user-card

альбо скрацаную версію:

ng g c user-card

Генератор стварае папку такую, як гэтая (непатрэбны = пасля называ файла спецыфікацыі — гэта памылка у прыводзе, а не частка называ):

user-card/
├── user-card.ts
├── user-card.html
├── user-card.css
└── user-card.spec.ts=

Новый стыль называння файла ёншы, чым старая прыемка user-card.component.ts. Тое, чы гэты файл тэстаў .spec.ts будзе створаны, залежыць ад настройкі тэставання проекта. Няўзабавныя версіі CLI, якія следуюць апошнім рэкамендацыям, таксама часта ствараюць назвы класоў без суфікса Component (напрыклад, UserCard); у прыкладах нижэй застосованы суфікс, і будзе працаваць любая з назв, як толькі імпорты будуць адпаведныя.

Як родны і дзецінскія компаненты вялі са светам

Рэальныя экраны — это дрэвы компанентаў, і ўмоўкненыя яны раздзеляць інфармацыю. Узглядзім родны компанент, які знае пра паўтароўца, і дзецінскі компанент, задача якога — адобразыць гэтага паўтароўца. Родным компанентам трэба спосаб перадаць даныя, а дзецінскаму — спосаб паведаміць пра тое, калі ўтварылася якая-небудзь ситуацыя, напрыклад, нажатыя кнопка.

Angular керуе напрамкамі дадзення: назоўні — за дапамогою элемента , а вниз — за дапамогою элемента :

Parent Component
      │
      │  Data
      │
      ▼
Child Component
      │
      │  Event
      │
      ▼
Parent Component

Для новага коду Angular рэкамендуе викорыстоўваць функціі input() і output(), якія базуюцца на сігналах. API з декоратарамі @Input() і @Output() таксама заступлівае, таму ў існуючых кодавых базах яны все ўжо будуць відзначаны.

Перадача дадзення назоўні за дапамогою input()

Пачніце з класу дочырньага компонента:

import { Component, input } from '@angular/core';
@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.html',
  styleUrl: './user-card.css'
})
export class UserCardComponent {
  name = input.required<string>();
}

Важлівай лініяю є заява пра элемент :

name = input.required<string>();

У яго зазначаецца, што UserCardComponent чакае строку name ад таго, хто яго выкарыстоўвае. Выкарыстанне input.required() робіць гэта выкарыстанне строгім: якщо родны элемент забудзе прыяўязаць name, Angular паведамляе пра адказку, у зваротнасці да таго, каб мовчкі адобразыць порожнюе значэнне. Для необавязковых поль input() прыймае значэнне за замовчанням.

Пакалі name ўособлівае ся як сігнал, а не проста як атрыбут, яго чытаюць праз вызыв. У класе вы пішаце:

this.name()

а ў шаблоне:

{{ name() }}

Забывчына аб парэнтэзах — частая пачатковая памылка; без іх шаблон адобразвае саму функцыю сігнала, а не яе значэнне. Шаблон дзецячага элемента выкарыстоўвае поль наступным чынам:

<div class="card">
  <h2>{{ name() }}</h2>
  <p>Welcome to Angular!</p>
</div>

У родным элементе вы прыяўязуеце значэнне да поля, калі размешчаеце дзецячы элемент:

<app-user-card [name]="userName" />

Якщо клас-родзіць мае:

userName = 'Yuvaraj';

дзецінскі клас отрымае гэты строк і яго адраджуе:

Yuvaraj

Квадратныя складанкі ўскладнююць гэта як прыяўнае звязваўанне атрыбута:

[name]="userName"

Яны паведамляюць Angular пра неабходнасць адраджэння значэння userName як выразу ў класе-родзіцы і падачы рэзультата ў атрыбут name дзецінскага класу. Без складанак Angular падаў бы проста літеральны текст userName. Паколькі атрыбут ўважаецца сігналам, дзецінскі клас автаматычна перадраджуецца кожны раз, калі змянюецца значэння ў класе-родзіцы.

Адправка запускоў па меры выходу дадзенняў за дапамогою output()

Тепер даўмо дзецінскаму класу кнопку, каб клас-родзіць могаў дазнацца, калі выбраны ўжотынка. Дзецінскі клас адгледвае выход і метод, які праказвае запуск чераз яго:

import { Component, input, output } from '@angular/core';
@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.html',
  styleUrl: './user-card.css'
})
export class UserCardComponent {
  name = input.required<string>();
  selected = output<string>();
  selectUser() {
    this.selected.emit(this.name());
  }
}

Адгледванне выходу визначае спецыяльны запуск, який перадае строку:

selected = output<string>();

Выклік emit запускае гэты адзіё і перадае текущую назву як його пэйлоад:

this.selected.emit(this.name());

Шаблон дзецявічага элемента вызывае selectUser() калі натыкаюцца на кнопку. Зверніце увагу на синтаксіс (click), які ўскладнюе прыўязку адзіяў у Angular:

<div class="card">
 <h2>{{ name() }}</h2>
  <button (click)="selectUser()">
    Select User
  </button>
</div>

Абяцельны элемент слухае спецыяльнае адзіё, выкарыстоўваючы той самы синтаксіс з кранікамі, але ўжо з назвай рэзультата:

<app-user-card
  [name]="userName"
  (selected)="userSelected($event)"
/>

Кранікі означаюць «слухаць гэтае адзіё», а $event храніць любую значэнне, якое выдала дзецявічая частка, тут — назву пользователя. Абяцельны элемент обробляе яго ў звычнай мэтадзе:

userSelected(name: string) {
  console.log('Selected user:', name);
}

Такое раздзеленне задач дазволяе дзецявічым элементам быть перавыкарыстоўванымі. Яны не ведаюць і не цікавяцца тым, што робіць абяцельны элемент з выбраным элементам; яны проста паведамляюць, што такое адзіё выйшла. Абяцельны элемент сам вырашае, чы рэўіставаць гэта, перейсці на іншую сторону чы апдэйтаваць свой стан.

Адаптаванне компонента на сторунку

Пашынны прыклад з’еднае всі элементы. У гэтай версіі компонента для прыветствавання викорыстоўваюцца інлайн-элементы template і styles у декоратары, замест адзінаковых файлаў, што ўдобна для дужа маленькіх компонентаў:

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

@Component({
  selector: 'app-welcome',
  template: `
    <div class="welcome-card">
      <h2>Welcome Component</h2>
      <p>This component was created using the <strong>app-welcome</strong> selector.</p>
    </div>
  `,
  styles: `
    .welcome-card {
      padding: 25px;
      margin-top: 20px;
      border-radius: 12px;
      background: #f5f5f5;
      text-align: center;
      font-family: Arial, sans-serif;
    }
    h2 {
      color: #dd0031;
    }
    p {
      font-size: 18px;
    }
  `
})
export class WelcomeComponent {}

Корневы компонент імпортуе WelcomeComponent у свой масэвы imports, і самэ гэта дазволяе селектару app-welcome быць викорыстоўным у яго шаблоне. Яго неадказаванне ўсё чащэ стае адной з прычын каштоўкі „элемент не ўпішаны“ у самастоятельных дапрацоўках:

import { Component } from '@angular/core';
import { WelcomeComponent } from './welcome';

@Component({
  selector: 'app-root',
  imports: [WelcomeComponent],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {}

У падсумку, корневы шаблон размешчае компонент за дапамогою яго селектара:

<div class="container">
<h1>Angular Components</h1>
  <p>
    Below is our custom Angular component:
  </p>
  <app-welcome></app-welcome>
</div>

Інлайн-шаблоны зберагаюць усё ў однам файле, але яны стаюць незручнымі, калі колькасць ліній маркапа перасягае певны ліміт. Розумна правіла — спачатку викорыстоўваць інлайн-шаблоны для маленькіх элементаў візуальнага адзначэння, а як толькі шаблону патрабуеяцца справжня структура, пераводзіць яго ў околачыя файлы .html і .css.

Ключовыя моманты

  • Компонента складаецца з класу TypeScript, шаблона і неабяжных стыляў, якія з’едынаюцца за дапамогою декоратара @Component().
  • У сучасным Angular стандартам являецца самастоятельная компонента; залежнасці прызначаюцца ў масіве imports кожнай компоненты, а не ў NgModule.
  • Селектар — это спецыяльны тэг, які викорыстоўваецца для размешчэння компоненты, а адпаведны элемент стае яе хостам.
  • Шаблоны прыўязуюцца да дадзеных класа, а Angular апдэйтуе відображэнне, калі гэтыя дадзеныя зміняюцца.
  • Стылі компанентаў кантролююцца за дапамогай імітаванага інкапсулявання, тады як правілы для всей аплікацыі размешчаюцца ў глобальным файле стыляў.
  • Ўжывайце input() (альбо input.required()) для перадачы дадзеных вярху і output() для адправкі запускаючых сыявоў; чытайце даннэ, якія прыходзяць, вызываючы іх як сыгналы.
  • Якща паглядзець так, аплікацыя Angular — это дрэва з компанентаў, кожны з якіх адпавядае за сваю частку інтэрфейсу і ведае абмен толькі через явныя вхідныя і выходныя элементы. Самэ гэтае строюванне дапамагае зберагчы вялікія аплікацыі Angular зрозумелымі па мере ўзрастання.