Angular 구성 요소 101 — 개요
게시 됨: 2022-03-11구성 요소는 처음부터 Angular에서 사용할 수 있습니다. 그러나 많은 사람들은 여전히 구성 요소를 잘못 사용하고 있습니다. 내 작업에서 나는 사람들이 그것들을 전혀 사용하지 않고 속성 지시문 대신 구성 요소를 만드는 등을 보았습니다. 이것은 저를 포함하여 주니어와 시니어 Angular 개발자 모두가 만드는 경향이 있는 문제입니다. 그래서 이 포스트는 내가 Angular를 배울 때와 같은 사람들을 위한 것입니다. 공식 문서나 구성 요소에 대한 비공식 문서 모두 사용 사례와 함께 구성 요소를 사용하는 방법과 시기를 설명하지 않기 때문입니다.
이 기사에서는 예제와 함께 Angular 구성 요소를 사용하는 올바른 방법과 잘못된 방법을 식별합니다. 이 게시물은 다음에 대한 명확한 아이디어를 제공해야 합니다.
- Angular 구성 요소의 정의
- 별도의 Angular 구성 요소 를 만들어야 하는 경우 그리고
- 별도의 Angular 구성 요소를 생성 해서는 안 되는 경우.
Angular 구성 요소의 올바른 사용법으로 넘어가기 전에 일반적으로 구성 요소 주제를 간단히 만지고 싶습니다. 일반적으로 말해서, 각각의 모든 Angular 애플리케이션에는 기본적으로 하나 이상의 구성 요소( root component
)가 있습니다. 거기에서 우리의 애플리케이션을 디자인하는 방법은 우리에게 달려 있습니다. 일반적으로 별도의 페이지에 대한 구성 요소를 만든 다음 각 페이지에는 별도의 구성 요소 목록이 포함됩니다. 일반적으로 구성 요소는 다음 기준을 충족해야 합니다.
- 데이터와 논리를 보유하는 클래스가 정의되어 있어야 합니다. 그리고
- 최종 사용자에 대한 정보를 표시하는 HTML 템플릿과 연결되어야 합니다.
다가오는 작업 과 완료된 작업 의 두 페이지가 있는 애플리케이션이 있는 시나리오를 상상해 봅시다. 예정된 작업 페이지에서 예정된 작업을 보고 "완료"로 표시하고 마지막으로 새 작업을 추가할 수 있습니다. 마찬가지로 완료된 작업 페이지에서 완료된 작업을 보고 "실행 취소"로 표시할 수 있습니다. 마지막으로 페이지 사이를 탐색할 수 있는 탐색 링크가 있습니다. 즉, 다음 페이지를 루트 구성 요소, 페이지, 재사용 가능한 구성 요소의 세 섹션으로 나눌 수 있습니다.
위의 스크린샷을 기반으로 애플리케이션의 구조가 다음과 같이 보일 것임을 분명히 알 수 있습니다.
── myTasksApplication ├── components │ ├── header-menu.component.ts │ ├── header-menu.component.html │ ├── task-list.component.ts │ └── task-list.component.html ├── pages │ ├── upcoming-tasks.component.ts │ ├── upcoming-tasks.component.html │ ├── completed-tasks.component.ts │ └── completed-tasks.component.html ├── app.component.ts └── app.component.html
따라서 구성 요소 파일을 위의 와이어프레임에 있는 실제 요소와 연결해 보겠습니다.
-
header-menu.component
및task-list.component
는 재사용 가능한 구성 요소이며 와이어프레임 스크린샷에서 녹색 테두리로 표시됩니다. -
upcoming-tasks.component
및completed-tasks.component
는 위의 와이어프레임 스크린샷에서 노란색 테두리로 표시되는 페이지입니다. 그리고 - 마지막으로
app.component
는 루트 구성 요소이며 와이어프레임 스크린샷에서 빨간색 테두리로 표시됩니다.
즉, 우리는 각각의 모든 구성 요소에 대해 별도의 논리와 디자인을 지정할 수 있습니다. 위의 와이어프레임을 기반으로 한 구성 요소 task-list.component
를 재사용하는 두 페이지가 있습니다. 특정 페이지에 표시해야 하는 데이터 유형을 어떻게 지정해야 하는지에 대한 질문이 제기될 것입니다. 다행스럽게도 구성 요소를 생성할 때 입력 및 출력 변수도 지정할 수 있으므로 이에 대해 걱정할 필요가 없습니다.
입력 변수
입력 변수는 상위 구성 요소에서 일부 데이터를 전달하기 위해 구성 요소 내에서 사용됩니다. 위의 예에서 task-list.component
에 대해 두 개의 입력 매개변수( tasks
및 listType
)를 가질 수 있습니다. 따라서 tasks
은 각 문자열을 별도의 행에 표시하는 문자열 목록이 되는 반면 listType
은 체크박스가 선택되어 있는지 여부를 표시하는 예정 또는 완료 됩니다. 아래에서 실제 구성 요소가 어떻게 보이는지에 대한 작은 코드 조각을 찾을 수 있습니다.
// task-list.component.ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. constructor() { } }
출력 변수
입력 변수와 유사하게 출력 변수를 사용하여 구성 요소 간에 일부 정보를 전달할 수도 있지만 이번에는 상위 구성 요소에 전달합니다. 예를 들어 task-list.component
의 경우 출력 변수 itemChecked
를 가질 수 있습니다. 항목이 선택되거나 선택되지 않은 경우 상위 구성 요소에 알립니다. 출력 변수는 이벤트 이미터여야 합니다. 아래에서 구성 요소가 출력 변수와 함께 표시되는 방식에 대한 작은 코드 조각을 찾을 수 있습니다.
// task-list.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. @Output() itemChecked: EventEmitter<boolean> = new EventEmitter(); @Output() tasksChange: EventEmitter<string[]> = new EventEmitter(); constructor() { } /** * Is called when an item from the list is checked. * @param selected---Value which indicates if the item is selected or deselected. */ onItemCheck(selected: boolean) { this.itemChecked.emit(selected); } /** * Is called when task list is changed. * @param changedTasks---Changed task list value, which should be sent to the parent component. */ onTasksChanged(changedTasks: string[]) { this.taskChange.emit(changedTasks); } }
task-list.component.ts
내용자식 구성 요소 사용 및 변수 바인딩
부모 구성 요소 내에서 이 구성 요소를 사용하는 방법과 다양한 유형의 변수 바인딩을 수행하는 방법을 살펴보겠습니다. Angular에는 입력 변수를 바인딩하는 두 가지 방법이 있습니다. 단방향 바인딩은 속성을 대괄호 []
로 묶어야 함을 의미하고 양방향 바인딩은 속성을 대괄호와 둥근 대괄호로 묶어야 함을 의미합니다. [()]
. 아래 예를 보고 구성 요소 간에 데이터를 전달할 수 있는 다양한 방법을 확인하십시오.
<h1>Upcoming Tasks</h1> <app-task-list [(tasks)]="upcomingTasks" [listType]="'upcoming'" (itemChecked)="onItemChecked($event)"></app-task-list>
upcoming-tasks.component.html
콘텐츠각 매개변수를 살펴보겠습니다.
-
tasks
매개변수는 양방향 바인딩을 사용하여 전달됩니다. 즉, 하위 구성 요소 내에서 작업 매개 변수가 변경된 경우 상위 구성 요소는 해당 변경 사항을upcomingTasks
Tasks 변수에 반영합니다. 양방향 데이터 바인딩을 허용하려면 "[inputParameterName]Change" 템플릿을 따르는 출력 매개변수를 생성해야 합니다. 이 경우에는tasksChange
입니다. -
listType
매개변수는 단방향 바인딩을 사용하여 전달됩니다. 즉, 자식 구성 요소 내에서는 변경할 수 있지만 부모 구성 요소에는 반영되지 않습니다. 구성 요소 내의 매개 변수에'upcoming'
값을 할당하고 대신 전달할 수 있음을 명심하십시오. 차이가 없을 것입니다. - 마지막으로
itemChecked
매개변수는 리스너 함수이며task-list.component
에서 onItemCheck 가 실행될 때마다 호출됩니다. 항목이 선택되면$event
값은true
이지만 선택하지 않으면false
값을 유지합니다.
보시다시피 일반적으로 Angular는 여러 구성 요소 간에 정보를 전달하고 공유하는 좋은 방법을 제공하므로 사용하는 것을 두려워해서는 안 됩니다. 그것들을 현명하게 사용하고 남용하지 않도록 하십시오.
별도의 각도 구성요소를 생성해야 하는 경우
앞서 언급했듯이 Angular 구성 요소를 사용하는 것을 두려워해서는 안되지만 확실히 현명하게 사용해야 합니다.
그렇다면 언제 Angular 컴포넌트를 생성 해야 할까요?
-
task-list.component
와 같이 구성 요소를 여러 위치에서 재사용할 수 있는 경우 항상 별도의 구성 요소를 만들어야 합니다. 우리는 그것들을 재사용 가능한 컴포넌트 라고 부릅니다. - 구성 요소가 상위 구성 요소를 더 읽기 쉽게 만들고 추가 테스트 범위를 추가할 수 있도록 하는 경우 별도의 구성 요소 생성을 고려해야 합니다. 코드 조직 구성 요소 라고 부를 수 있습니다.
- 자주 업데이트할 필요가 없고 성능을 높이고 싶은 페이지의 일부가 있는 경우 항상 별도의 구성 요소를 만들어야 합니다. 이것은 변경 감지 전략과 관련이 있습니다. 최적화 구성 요소 라고 부를 수 있습니다.
이 세 가지 규칙은 새 구성 요소를 만들어야 하는지 식별하는 데 도움이 되며 구성 요소의 역할에 대한 명확한 비전을 자동으로 제공합니다. 이상적으로는 구성 요소를 생성할 때 응용 프로그램 내에서 구성 요소의 역할이 무엇인지 이미 알고 있어야 합니다.
재사용 가능한 구성 요소 의 사용법을 이미 살펴보았으므로 코드 구성 구성 요소 의 사용법을 살펴보겠습니다. 등록 양식이 있고 양식 하단에 이용 약관 이 있는 상자가 있다고 가정해 보겠습니다. 일반적으로 Legalese는 매우 커서 공간을 많이 차지하는 경향이 있습니다(이 경우 HTML 템플릿에서). 따라서 이 예를 보고 잠재적으로 변경할 수 있는 방법을 살펴보겠습니다.
처음에는 등록 양식과 이용 약관 자체를 포함하여 모든 것을 포함하는 하나의 구성 요소인 registration.component
가 있습니다.

<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <div class="terms-and-conditions-box"> Text with very long terms and conditions. </div> <button (click)="onRegistrate()">Registrate</button>
이제 템플릿이 작아 보이지만 "매우 긴 조건의 텍스트"를 1000단어가 넘는 실제 텍스트로 대체한다고 상상해 보십시오. 그러면 파일 편집이 어렵고 불편할 것입니다. 이에 대한 빠른 솔루션이 있습니다. 약관과 관련된 모든 항목을 포함하는 새로운 구성 요소 terms-and-conditions.component
를 만들 수 있습니다. 그러면 terms-and-conditions.component
에 대한 HTML 파일을 살펴보겠습니다.
<div class="terms-and-conditions-box"> Text with very long terms and conditions. </div>
이제 registration.component
를 조정하고 그 안에 terms-and-conditions.component
.component를 사용할 수 있습니다.
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()">Registrate</button>
축하합니다! registration.component
의 크기를 수백 줄로 줄이고 코드를 읽기 쉽게 만들었습니다. 위의 예에서는 컴포넌트의 템플릿을 변경했지만 컴포넌트의 로직에도 동일한 원칙을 적용할 수 있습니다.
마지막으로 최적화 구성 요소 의 경우 변경 감지를 이해하는 데 필요한 모든 정보와 적용할 수 있는 특정 사례를 제공하므로 이 게시물을 검토하는 것이 좋습니다. 자주 사용하지는 않지만 경우에 따라 여러 구성 요소에 대한 정기 검사가 필요하지 않을 때 건너뛸 수 있다면 성능 면에서 윈윈입니다.
즉, 항상 별도의 구성 요소를 생성해서는 안 되므로 별도의 구성 요소를 생성하지 않아야 하는 경우를 살펴보겠습니다.
별도의 Angular 구성 요소 생성을 피해야 하는 경우
별도의 Angular 구성 요소를 만들 것을 제안하는 세 가지 주요 사항이 있지만 별도의 구성 요소를 만드는 것을 피하는 경우도 있습니다.
별도의 컴포넌트를 생성하면 안 되는 경우를 쉽게 이해할 수 있도록 글머리 기호를 다시 살펴보겠습니다.
- DOM 조작을 위한 구성 요소를 만들면 안 됩니다. 이러한 경우 속성 지시문을 사용해야 합니다.
- 코드를 더 혼란스럽게 만들 경우 구성 요소를 생성 해서는 안 됩니다. 이것은 코드 구성 구성 요소 의 반대입니다.
이제 더 자세히 살펴보고 예제에서 두 경우를 모두 확인해 보겠습니다. 클릭했을 때 메시지를 기록하는 버튼이 있다고 가정해 봅시다. 이것은 실수할 수 있으며 이 특정 기능에 대해 특정 버튼과 작업을 보유하는 별도의 구성 요소가 생성될 수 있습니다. 먼저 잘못된 접근 방식을 확인하겠습니다.
// log-button.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-log-button', templateUrl: 'log-button.component.html' }) export class LogButtonComponent { @Input() name: string; // Name of the button. @Output() buttonClicked: EventEmitter<boolean> = new EventEmitter(); constructor() { } /** * Is called when button is clicked. * @param clicked - Value which indicates if the button was clicked. */ onButtonClick(clicked: boolean) { console.log('I just clicked a button on this website'); this.buttonClicked.emit(clicked); } }
log-button.component.ts
따라서 이 컴포넌트는 다음과 같은 html 보기와 함께 제공됩니다.
<button (click)="onButtonClick(true)">{{ name }}</button>
log-button.component.html
의 템플릿보시다시피 위의 예가 작동하고 보기 전체에서 위의 구성 요소를 사용할 수 있습니다. 그것은 기술적으로 당신이 원하는 것을 할 것입니다. 그러나 여기서 올바른 해결책은 지시문을 사용하는 것입니다. 이렇게 하면 작성해야 하는 코드의 양을 줄일 수 있을 뿐만 아니라 버튼뿐만 아니라 원하는 요소에 이 기능을 적용할 수 있는 가능성을 추가할 수 있습니다.
import { Directive } from '@angular/core'; @Directive({ selector: "[logButton]", hostListeners: { 'click': 'onButtonClick()', }, }) class LogButton { constructor() {} /** * Fired when element is clicked. */ onButtonClick() { console.log('I just clicked a button on this website'); } }
logButton
지시문 이제 지시문을 만들면 응용 프로그램 전체에서 사용하고 원하는 요소에 할당할 수 있습니다. 예를 들어, registration.component
를 재사용합시다.
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()" logButton>Registrate</button>
logButton
지시문 이제 별도의 구성 요소를 생성하지 않아야 하는 두 번째 경우를 살펴보겠습니다. 이는 코드 최적화 구성 요소 와 반대입니다. 새로 생성된 구성 요소가 코드를 더 복잡하고 크게 만드는 경우 생성할 필요가 없습니다. 예를 들어 registration.component
를 살펴보겠습니다. 그러한 경우 중 하나는 수많은 입력 매개변수를 사용하여 레이블 및 입력 필드에 대해 별도의 구성 요소를 만드는 것입니다. 이 나쁜 습관을 살펴봅시다.
// form-input-with-label.component.ts import { Component, Input} from '@angular/core'; @Component({ selector: 'app-form-input-with-label', templateUrl: 'form-input-with-label.component.html' }) export class FormInputWithLabelComponent { @Input() name: string; // Name of the field @Input() id: string; // Id of the field @Input() label: string; // Label of the field @Input() type: 'text' | 'password'; // Type of the field @Input() model: any; // Model of the field constructor() { } }
form-input-with-label.component
그리고 이것은 이 구성 요소에 대한 보기일 수 있습니다.
<label for="{{ id }}">{{ label }}</label><br /> <input type="{{ type }}" name="{{ name }}" [(ngModel)]="model" /><br />
form-input-with-label.component
물론, registration.component
에서 코드의 양이 줄어들겠지만, 코드의 전체 논리를 더 쉽게 이해하고 읽기 쉽게 만들 수 있습니까? 코드를 이전보다 불필요하게 복잡하게 만든다는 것을 분명히 알 수 있다고 생각합니다.
다음 단계: Angular 구성 요소 102?
요약하자면: 구성 요소를 사용하는 것을 두려워하지 마십시오. 달성하고자 하는 것에 대한 명확한 비전이 있는지 확인하십시오. 위에 나열한 시나리오는 가장 일반적인 시나리오이며 가장 중요하고 일반적인 시나리오라고 생각합니다. 그러나 귀하의 시나리오는 고유할 수 있으며 정보에 입각한 결정을 내리는 것은 귀하에게 달려 있습니다. 좋은 결정을 내리기 위해 충분히 배웠기를 바랍니다.
Angular의 변경 감지 전략과 OnPush 전략에 대해 자세히 알아보려면 Angular 변경 감지 및 OnPush 전략을 읽는 것이 좋습니다. 컴포넌트와 밀접한 관련이 있으며 이미 포스트에서 언급했듯이 애플리케이션의 성능을 크게 향상시킬 수 있습니다.
구성 요소는 Angular가 제공하는 지시문의 일부일 뿐이므로 속성 지시문 과 구조적 지시문 도 함께 알아가는 것이 좋습니다. 모든 지시문을 이해하면 프로그래머가 더 나은 코드를 더 쉽게 작성할 수 있습니다.