AngularDart(我们通常在这个文档中简单地称为Angular)是一个框架,用于在HTML和Dart中构建客户端应用程序。它是作为Angular包发布的,与 其他许多Dart包一样,可以通过Pub工具获得。
您可以通过使用Angular的标记组合HTML 模板,编写组件类来管理这些模板,在服务中添加应用程序逻辑以及在模块中装入组件和服务来编写Angular应用程序。
然后,通过引导根模块启动应用程序。 Angular接管,根据您提供的说明在浏览器中呈现您的应用内容,并响应用户交互。 当然,除此之外还有更多。 您将在后面的页面中了解详细信息。 现在就着眼于大局。
架构图标识了Angular应用程序的八个主要构建块:
了解这些积木,你就在路上。
Angular应用程序是模块化的; 也就是说,应用程序由许多模块组装而成。
在本指南中,术语module是指Dart编译单元,例如库或包。 如果Dart文件除去library或part命令,那么该文件本身就是一个库,因此也是一个编译单元。有关编译单元的更多信息,请参阅Dart语言规范中的“库和脚本”一章。
每个Angular应用程序至少有一个模块,即根模块。 虽然根模块可能是小应用程序中的唯一模块,但大多数应用程序都有更多的功能模块,每个模块都是专用于应用程序域,工作流程或紧密相关的一组功能的一致代码块。
最简单的根模块定义了一个单独的根组件类,例如:lib / app_component.dart(class)
class AppComponent {}
按照惯例,根组件的名称是AppComponent。
Angular全体就像是Angular包内的库的集合。 主要的Angular库是angular,大多数app模块导入如下:
import 'package:angular/angular.dart';
Angular包有其他重要的库,如angular.security。
一个组件控制屏幕中的一小块视图。
例如,以下视图由组件控制:
您可以在一个类中定义一个组件的应用程序逻辑 - 它支持视图的功能。 该类通过属性和方法的API与视图交互。
例如,这个HeroListComponent有一个heroes属性,返回从服务中获取的英雄列表。 HeroListComponent还有一个selectHero()方法,当用户点击从列表中选择一个英雄时,它会设置一个selectedHero属性。lib/src/hero_list_component.dart (class)
class HeroListComponent implements OnInit {
List<Hero> heroes;
Hero selectedHero;
final HeroService _heroService;
HeroListComponent(this._heroService);
void ngOnInit() {
heroes = _heroService.getHeroes();
}
void selectHero(Hero hero) {
selectedHero = hero;
}
}
Angular创建,更新和销毁组件如同用户在应用程序中行走。 您的应用程序可以通过可选的生命周期挂钩在此生命周期中的每个时刻采取行动,如上面声明的ngOnInit()。
您可以使用其配套模板定义组件的视图。 模板是一种HTML形式,告诉Angular如何呈现组件。
模板看起来像普通的HTML,除了一些不同之处。 这是我们的HeroListComponent的一个模板:
lib/src/hero_list_component.html
<h2>Hero List</h2>
<p><i>Pick a hero from the list</i></p>
<ul>
<li *ngFor="let hero of heroes" (click)="selectHero(hero)">
{{hero.name}}
</li>
</ul>
<hero-detail *ngIf="selectedHero != null" [hero]="selectedHero"></hero-detail>
虽然这个模板使用了典型的HTML元素,如<h2>和<p>,但它也有一些不同之处。 类似于* ngFor,{{hero.name}},(click),[hero]和<hero-detail>的代码使用Angular的模板语法。 在模板的最后一行,<hero-detail>标签是一个自定义元素,代表一个新的组件HeroDetailComponent。
HeroDetailComponent是与您正在审阅的HeroListComponent不同的组件。 HeroDetailComponent(代码未显示)显示关于特定英雄的详情,这是用户从HeroListComponent提供的列表中选择的英雄。 HeroDetailComponent是HeroListComponent的一个子项。
注意<hero-detail>是如何在原生HTML元素中合适的存放。 自定义组件与原生HTML在相同的布局中无缝混合。
元数据告诉Angular如何处理一个类。
回顾HeroListComponent的代码,你可以看到它只是一个类。 没有一个框架的痕迹,没有Angular的特定代码。 实际上,HeroListComponent实际上只是一个类。 直到你告诉Angular它是一个组件。要告诉Angular HeroListComponent是一个组件,请将元数据附加到该类。在Dart中,您可以使用注解附加元数据。
以下是HeroListComponent的一些元数据,@Component注解标识紧接着它下面的类作为一个组件类:
lib/src/hero_list_component.dart (metadata)
@Component(
selector: 'hero-list',
templateUrl: 'hero_list_component.html',
directives: const [CORE_DIRECTIVES, formDirectives, HeroDetailComponent],
providers: const [HeroService],
)
class HeroListComponent implements OnInit {
// ···
}
注解通常具有配置参数。 @Component注解需要参数提供Angular需要的信息来创建和呈现组件及其视图。 以下是一些可能的@Component参数:
@Component中的元数据告诉Angular从哪里获取为组件指定的主要构建块。
模板,元数据和组件一起描述一个视图。
以类似的方式应用其他元数据注解以指导Angular行为。 @Injectable,@Input和@Output是一些比较流行的注解。
建筑外包是你必须添加元数据到你的代码,以便Angular知道该怎么做。
如果没有框架,您将负责将数据值推送到HTML控件中,并将用户响应转化为操作和值更新。 用手写这样的推/拉逻辑是单调乏味,容易出错的,而且像任何经验丰富的jQuery程序员都能证明的那样是一场恶梦。
Angular支持数据绑定,这是一种协调模板部分与组件部分的机制。 添加绑定标记到模板HTML告诉Angular如何连接双方。
如图所示,有四种形式的数据绑定语法。 每个表单都有一个方向 - 从DOM到DOM,或者在两个方向。
HeroListComponent示例模板有三种形式: lib / src / hero_list_component.html(binding)
<li>{{hero.name}}</li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>
双向数据绑定是一个重要的第四种形式,它使用ngModel指令将属性和事件绑定在一个符号中。 以下是HeroDetailComponent模板的一个例子:lib/src/hero_detail_component.html (ngModel)
<input [(ngModel)]="hero.name">
在双向绑定中,与属性绑定一样,数据属性值将从组件输入到输入框中。 用户的更改也会返回到组件,将属性重置为最新值,就像事件绑定一样。 Angular在每个JavaScript事件循环中处理所有数据绑定,从应用程序组件树的根到所有子组件。
数据绑定在模板及其组件之间的通信中起着重要的作用。
数据绑定对于父组件和子组件之间的通信也很重要。
Angular模板是动态的。 当Angular呈现它们时,它根据指令给出的指示转换DOM。
指令是一个带有@Directive注解的类。 一个组件是一个指令与模板; 一个@Component注解实际上是一个@Directive注解,扩展了面向模板的特性。
虽然组件在技术上是指令,但组件对于Angular应用程序来说是非常独特和重要的,所以这种架构概述将组件与指令分开。
还有其他两种指令:结构和属性指令。 它们倾向于以属性的形式出现在元素标签内,有时候以名称的形式出现,但更常见的是作为赋值或绑定的目标。 结构指令通过添加,删除和替换DOM中的元素来改变布局。 示例模板使用两个内置的结构指令: lib / src / hero_list_component.html(structural)
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero != null"></hero-detail>
在Dart中,唯一值为true的是布尔值true; 所有其他值是错误的。 JavaScript和TypeScript相反,将诸如1和大多数非空对象的值视为true。 出于这个原因,这个应用程序的JavaScript和TypeScript版本可以使用selectedHero作为* ngIf表达式的值。 Dart版本必须使用布尔运算符!=替换。
属性指令会改变现有元素的外观或行为。 在模板中,它们看起来像常规的HTML属性,因此也就是名称。 实现双向数据绑定的ngModel指令是一个属性指令的例子。 ngModel通过设置其显示值属性并响应更改事件来修改现有元素(通常是<input>)的行为。lib/src/hero_detail_component.html (ngModel)
<input [(ngModel)]="hero.name">
Angular还有一些指令可以改变布局结构(例如,ngSwitch)或修改DOM元素和组件的方面(例如ngStyle和ngClass)。 当然,你也可以编写你自己的指令。 像HeroListComponent这样的组件是一种自定义指令。
服务是一个广泛的类别,包含您的应用程序所需的任何值,功能或特征。
几乎任何东西都可以成为服务。 服务通常是一个狭义的,明确的目的。 它应该做一些具体的事情,并做好。
例子包括:
Angular中没有特别指定服务。 Angular没有定义服务。 没有服务基础类,没有地方注册服务。 然而,服务是任何Angular的应用程序的基础。 组件占据了服务的半壁江山。 以下是一个输出到浏览器控制台的日志服务类的示例:lib/src/logger_service.dart (class)
class Logger {
void log(Object msg) => window.console.log(msg);
void error(Object msg) => window.console.error(msg);
void warn(Object msg) => window.console.warn(msg);
}
这是一个HeroService,使用Future来获取英雄。 HeroService取决于日志服务和另一个处理服务器频繁通信工作的BackendService。 lib / src / hero_service.dart(class)
class HeroService {
final BackendService _backendService;
final Logger _logger;
final heroes = <Hero>[];
HeroService(this._logger, this._backendService);
List<Hero> getHeroes() {
_backendService.getAll(Hero).then((heroes) {
_logger.log('Fetched ${heroes.length} heroes.');
this.heroes.addAll(heroes as List<Hero>); // fill cache
});
return heroes;
}
}
服务无处不在。
组件类应该是精益的。 他们不从服务器获取数据,验证用户输入或直接登录到控制台。 他们将这些任务委托给服务。
一个组件的工作是启用用户体验,仅此而已。 它在视图(由模板呈现)和应用程序逻辑(通常包括模型的一些概念)之间起中介作用。 一个好的组件提供了数据绑定的属性和方法。 它委托一切不重要的服务。
Angular不强制执行这些原则。 如果您用3000行代码编写“kitchen sink”组件,它不会抱怨。
Angular通过简单地将应用程序逻辑分解为服务,并通过依赖注入将这些服务提供给组件,从而帮助您遵循这些原则。
依赖注入是一种提供一个类的新实例的方法,它需要完整的依赖关系。 大多数依赖是服务。 Angular使用依赖注入来为新组件提供他们需要的服务。
Angular可以通过查看构造函数参数的类型来判断组件需要哪些服务。 例如,你的HeroListComponent的构造函数需要一个HeroService:lib/src/hero_list_component.dart (constructor)
final HeroService _heroService;
HeroListComponent(this._heroService);
当Angular创建一个组件时,它首先要求一个注入器来提供组件需要的服务。
注入器维护一个先前创建的服务实例的容器。 如果请求的服务实例不在容器中,那么在将服务返回给Angular之前,注入器将创建一个并将其添加到容器中。 当所有请求的服务已经解析并返回时,Angular可以用这些服务作为参数调用组件的构造函数。 这是依赖注入。 HeroService注入的过程看起来有点像这样:
如果注射器没有HeroService,它如何知道如何制作一个?
简而言之,您必须事先在注入器中注册HeroService的提供者。 提供者是可以创建或返回服务的东西,通常是服务类本身。
无论应用程序组件树中的级别如何,您都可以在引导期间或组件中注册提供程序。
注册提供者最常见的方法是使用@Component注解providers参数在组件级别:lib/app_component.dart
@Component(
// ···
providers: const [BackendService, HeroService, Logger],
)
class AppComponent {}
使用组件注册意味着您将获得该组件的每个新实例的服务新实例。 通过组件提供的服务与应用程序组件树中的所有组件的后代共享。 引导时注册提供程序的情况非常少见。 有关详细信息,请参阅依赖注入页面的配置注入部分。 关于依赖注入的要点:
包起来 您已经了解了关于Angular应用程序的八个主要构建块的基础知识:
这是一个Angular应用程序中所有其他应用程序的基础,而且这足够了。但它并不包括你需要知道的一切。
以下是其他重要的Angular功能和服务的简短字母顺序列表。