本文概述
从一开始就可以在Angular中使用组件。但是, 许多人仍然发现自己使用了错误的组件。在我的工作中, 我看到人们根本不使用它们, 而是创建组件而不是属性指令, 等等。这些都是Angular的初级和高级开发人员都会遇到的问题, 包括我自己在内。因此, 本文是针对像我在学习Angular时一样的人的, 因为有关组件的官方文档和非官方文档都没有用用例说明如何以及何时使用组件。
在本文中, 我将通过示例来确定使用Angular组件的正确和不正确的方法。这篇文章应该给你一个清晰的想法:
- Angular组件的定义;
- 什么时候应该创建单独的Angular组件;和
- 不应该创建单独的Angular组件时。
在我们开始正确使用Angular组件之前, 我想简要地介绍一下组件主题。一般来说, 每个Angular应用程序默认都有至少一个组件-根组件。从那里开始, 如何设计应用程序取决于我们。通常, 你将为单独的页面创建一个组件, 然后每个页面将包含一个单独的组件的列表。根据经验, 组件必须满足以下条件:
- 必须定义一个包含数据和逻辑的类;和
- 必须与显示最终用户信息的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是可重用的组件, 它们在线框屏幕截图中以绿色边框显示;
- 即将到来的任务。组件和完成的任务。组件是页面, 在上面的线框屏幕截图中以黄色边框显示;和
- 最后, app.component是根组件, 它在线框屏幕截图中显示为红色边框。
因此, 话虽如此, 我们可以为每个组件指定单独的逻辑和设计。基于上面的线框, 我们有两个页面可以重复使用一个组件-task-list.component。将会出现一个问题-我们如何指定应该在特定页面上显示的数据类型?幸运的是, 我们不必担心, 因为在创建组件时, 你还可以指定Input和Output变量。
输入变量
组件内使用输入变量从父组件传入一些数据。在上面的示例中, 我们可以为task-list.component提供两个输入参数-tasks和listType。因此, 任务将是一个字符串列表, 它将在单独的行上显示每个字符串, 而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>
可能的即将出现的task.task.component.html内容
我们来看一下每个参数:
- 使用双向绑定传递task参数。这意味着, 如果在子组件中更改了tasks参数, 则父组件将在即将到来的Tasks变量上反映这些更改。要允许双向数据绑定, 你必须创建一个输出参数, 该输出参数紧随模板” [inputParameterName] Change”(在本例中为taskChange)。
- 使用单向绑定传递listType参数。这意味着它可以在子组件中进行更改, 但不会反映在父组件中。请记住, 我可以将值”即将来临”分配给组件中的一个参数并传递给它, 这没有什么区别。
- 最后, itemChecked参数是一个侦听器函数, 并且在task-list.component上执行onItemCheck时将调用它。如果选中一项, 则$ event的值将为true, 但是如果未选中该项, 则它将的值为false。
如你所见, 通常, Angular提供了一种在多个组件之间传递和共享信息的好方法, 因此你不必害怕使用它们。只要确保明智地使用它们, 不要过度使用它们。
何时创建单独的Angular组件
如前所述, 你不应该害怕使用Angular组件, 但绝对应该明智地使用它们。
明智地使用Angular组件
那么什么时候应该创建Angular组件呢?
- 如果可以在多个地方重复使用该组件, 则应该始终创建一个单独的组件, 例如我们的task-list.component。我们称它们为可重用组件。
- 如果该组件将使父组件更具可读性并允许他们添加其他测试覆盖范围, 则应考虑创建单独的组件。我们可以称它们为代码组织组件。
- 如果页面的一部分不需要经常更新并且想要提高性能, 则应该始终创建一个单独的组件。这与变更检测策略有关。我们可以称它们为优化组件。
这三个规则可帮助我确定是否需要创建新组件, 并且它们自动使我清楚地了解该组件的Angular色。理想情况下, 在创建组件时, 你应该已经知道它在应用程序中的作用。
由于我们已经研究了可重用组件的用法, 因此让我们来研究代码组织组件的用法。假设我们有一个注册表格, 并且在表格底部有一个带有条款和条件的框。通常, 法务人员通常非常大, 占用大量空间-在这种情况下, 使用HTML模板。因此, 让我们看一下这个示例, 然后看看我们如何可能对其进行更改。
最初, 我们有一个组件(registration.component), 该组件包含所有内容, 包括注册表格以及条款和条件本身。
<h2>Registration</h2>
<label for="username">Username</label><br />
<input type="text" name="username" id="username" [(ngModel)]="username" /><br />
<label for="password">Password</label><br />
<input type="password" name="password" id="password" [(ngModel)]="password" /><br />
<div class="terms-and-conditions-box">
Text with very long terms and conditions.
</div>
<button (click)="onRegistrate()">Registrate</button>
将</ code> registration.component </ code>分为多个组件之前的初始状态
现在, 模板看起来很小, 但是想像一下, 如果我们将”具有很长的条款和条件的文本”替换为包含1000个单词以上的实际文本, 这将使文件编辑变得困难且令人不舒服。为此, 我们有一个快速的解决方案-我们可以发明一个新的term-and-conditions.component组件, 其中包含与条款和条件相关的所有内容。因此, 让我们看一下term-and-conditions.component的HTML文件。
<div class="terms-and-conditions-box">
Text with very long terms and conditions.
</div>
新建的</ code> terms-and-conditions.component </ code> HTML模板
现在, 我们可以调整registration.component并在其中使用term-and-conditions.component。
<h2>Registration</h2>
<label for="username">Username</label><br />
<input type="text" name="username" id="username" [(ngModel)]="username" /><br />
<label for="password">Password</label><br />
<input type="password" name="password" id="password" [(ngModel)]="password" /><br />
<app-terms-and-conditions></app-terms-and-conditions>
<button (click)="onRegistrate()">Registrate</button>
使用代码组织组件更新了</ code> registration.component.ts </ code>模板
恭喜你!我们刚刚将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" id="username" [(ngModel)]="username" /><br />
<label for="password">Password</label><br />
<input type="password" name="password" id="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 }}" id="{{ id }}" [(ngModel)]="model" /><br />
form-input-with-label.component的视图
当然, registration.component中的代码量会减少, 但这是否会使代码的整体逻辑更容易理解和可读?我认为我们可以清楚地看到, 它使代码不必要地比以前更加复杂。
后续步骤:Angular Components 102?
总结一下:不要害怕使用组件;只需确保对要实现的目标有清晰的愿景。上面列出的场景是最常见的场景, 我认为它们是最重要和最常见的场景;但是, 你的方案可能是独特的, 并且取决于你做出明智的决定。希望你学到了足够的知识, 可以做出明智的决定。
如果你想进一步了解Angular的变更检测策略和OnPush策略, 建议阅读Angular变更检测和OnPush策略。它与组件密切相关, 并且正如我在文章中已经提到的那样, 它可以显着提高应用程序的性能。
由于组件只是Angular提供的指令的一部分, 因此也很了解属性指令和结构指令将是很棒的。了解所有指令最有可能使程序员更容易一起编写更好的代码。
评论前必须登录!
注册