angular开源代码
NgModules是Angular的核心概念,它是每个应用程序的一部分,有助于为编译器和应用程序运行时连接一些重要的细节。 它们对于将代码组织成功能,延迟加载路由以及创建可重用的库特别有用。
在本指南中,我们将通过一些示例介绍NgModules的主要用法,向您展示如何在Angular项目中使用它们! 本指南假定您具有Angular的使用知识。
JavaScript模块不是NgModules
首先,让我们澄清一下什么是JavaScript模块(有时称为ES6模块)。 它们是一种语言构造,可以更轻松地组织代码。
最基本的Javascript模块是包含import
或export
关键字JavaScript文件,这些文件会导致在该文件内定义的对象是私有的,除非您将其导出。 我鼓励您查看上面的链接以加深了解,但是从本质上讲,这是一种组织代码并轻松共享代码的方法,而无需依赖可怕的全局范围。
当您使用TypeScript创建Angular应用程序时,每次在源中使用import
或export
,都会将其视为JavaScript模块。 TypeScript能够为您处理模块加载。
注意:为了使本文更清楚明了,我将始终使用全名来引用JavaScript模块和NgModules。
基本的NgModule,AppModule
让我们从每个Angular应用程序中存在的基本NgModule开始,即AppModule
(在任何新的Angular应用程序中默认生成)。 看起来就像您在这里看到的那样:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Angular使用修饰符来定义在编译期间需要了解的元数据。 要定义NgModue,只需在类上方添加@NgModule()
装饰器。 该类可能并不总是空的,但通常是空的。 但是,您需要为NgModule定义具有一些属性的对象才能执行任何操作。
当应用程序启动时,需要给它一个NgModule进行实例化。 如果查看应用程序的主文件(通常也称为main.ts
),则会看到platformBrowserDynamic().bootstrapModule(AppModule)
,这是应用程序注册和启动AppModule
(可以命名为任意名称,但是几乎总是以此命名)。
NgModule的属性
NgModule API文档页面概述了定义NgModule时可以传递的属性,但我们也会在此处进行介绍。 它们都是可选的,但是您需要为NgModule定义其中至少一个的值才能执行任何操作。
providers
providers
是一个数组,其中包含可用于此NgModule的所有提供程序 (可注入服务)的列表。 提供者具有作用域,并且如果它们在延迟加载的NgModule中列出,则在该NgModule之外不可用。
declarations
declarations
数组应包含此NgModule定义的所有指令,组件或管道的列表。 这使编译器可以找到这些项目并确保将它们正确捆绑在一起。 如果这是根NgModule,则声明可用于所有NgModule。 否则,它们仅对同一NgModule可见。
imports
如果您的NgModule依赖于另一个NgModule的任何其他对象,则必须将其添加到imports
数组。 这样可以确保编译器和依赖项注入系统了解导入的项目。
exports
使用exports
数组,您可以定义哪些指令,组件和管道可用于导入此NgModule的任何NgModule。 例如,在UI库中,您将导出组成该库的所有组件。
entryComponents
任何需要在运行时加载的组件都必须添加到entryComponents
列表中。 本质上,这将创建组件工厂并在需要动态加载时存储它。 您可以从文档中了解有关如何动态加载组件的更多信息。
bootstrap
您可以定义任意数量的组件,以便在首次加载应用程序时进行引导。 通常,您只需要引导主根组件(通常称为AppComponent
),但是如果您有多个根组件,则将在此处声明每个根组件。 通过将组件添加到bootstrap
数组,它也被添加到entryComponents
列表中并进行了预编译。
schemas
模式是定义Angular编译模板的方式,并且当它发现不是标准HTML或已知组件的元素时是否会抛出错误。 默认情况下,Angular在模板中找不到未知元素时会引发错误,但是您可以通过将架构设置为NO_ERRORS_SCHEMA (允许所有元素和属性)或CUSTOM_ELEMENTS_SCHEMA (允许任何元素)来更改此行为或名称中带有-
属性)。
id
此属性使您可以为NgModule提供唯一的ID,该ID可用于检索模块工厂引用。 目前,这是一种罕见的用例。
NgModule示例
为了说明NgModule与Angular结合使用的方式,让我们看一组示例,这些示例向您展示如何轻松处理各种用例。
功能NgModules
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
除了AppModule之外, AppModule
最基本用例是功能NgModules (通常称为功能模块,但试图保持术语一致)。 它们有助于将应用程序的各个部分分开,因此强烈建议使用。 在大多数情况下,它们与主应用程序NgModule相同。 让我们看一下基本的功能NgModule:
@NgModule({
declarations: [
ForumComponent,
ForumsComponent,
ThreadComponent,
ThreadsComponent
],
imports: [
CommonModule,
FormsModule,
],
exports: [
ForumsComponent
]
providers: [
ForumsService
]
})
export class ForumsModule { }
这个简单的功能NgModule定义了四个组件,一个提供程序,并导入了组件和服务所需的两个模块。 这些共同构成了应用程序论坛部分的必要部分。
providers
中的项目可用于任何导入要注入的ForumsModule
,但是了解每个NgModule将获得自己的服务实例非常重要。 这与根NgModule中列出的提供程序不同,从该提供程序中您将始终获得相同的实例(除非重新提供了该实例)。 在这里,重要的是要理解依赖注入,尤其是层次依赖注入 。 很容易想到您将获得服务的相同实例并更改其属性,但从未在应用程序的其他位置看到更改。
如我们先前所知, declarations
中的项目实际上不可用于其他NgModule,因为它们是此NgModule专用的。 要解决此问题,您可以选择导出要在其他NgModule中使用的那些声明,例如在此代码段中仅导出ForumsComponent
。 现在,在任何其他功能NgModule中,您可以放置<app-forums></app-forums>
(或该组件的任何选择器)以在模板中显示ForumsComponent
。
另一个主要区别是ForumsModule
导入CommonModule而不是BrowserModule 。 BrowserModule
仅应在根NgModule处导入,但CommonModule
包含核心的Angular指令和管道(例如NgFor
和Date
管道)。 如果您的Feature NgModule不使用任何这些功能,则实际上并不需要CommonModule
。
现在,当您想在项目中使用ForumsModule
时,需要像在此处看到的那样将其导入到AppModule
:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ForumsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
然后,将此NgModule导入到主AppModule
以正确加载它,其中包括ForumsModule
提供程序数组中的项目以及任何导出的项目,供您在应用程序中使用。
使用Angular CLI时,可以通过为新的NgModule运行生成器来轻松生成Feature NgModule:
ng generate module path/to/module/feature
您可以按照自己认为合适的任何方式组织Feature NgModule,但是通常的建议是将用于同一视图的相似内容归为一组。 我尝试制作少量的Feature NgModules来保存常用的东西,然后针对应用程序的每个主要功能,将更多的精力放在NgModules上。
用路由延迟加载NgModules
有时,您只想在用户需要时才加载代码,而通过Angular,当前可以通过将路由器和Feature NgModules一起使用来加载代码。 当用户请求特定路由时,路由器可以延迟加载NgModule。 如果您不熟悉路由,请参阅有关使用Angular进行路由的入门 。
最好的开始方法是为路由的唯一部分创建功能NgModule。 如果它们几乎总是一起使用,您甚至可能希望将多个路由组合在一起。 例如,如果您有一个客户帐户页面,其中包含几个用于管理帐户详细信息的子页面,则很有可能将它们声明为同一NgModule的一部分。
定义NgModule本身的方式没有什么区别,只不过需要使用RouterModule.forChild()
定义一些路由。 您应该有一个路径为空的路由,该路由的作用类似于此Feature NgModule的根路由,所有其他路由都将挂起:
@NgModule({
declarations: [
ForumComponent,
ForumsComponent,
],
imports: [
CommonModule,
FormsModule,
RouterModule.forChild([
{path: '', component: ForumsComponent},
{path: ':forum_id', component: ForumComponent}
])
],
providers: [
ForumsService
]
})
export class ForumsModule { }
行为上有一个重要变化,与提供者在应用程序中的注册方式无关。 由于这是一个延迟加载的NgModule,因此提供程序对其余应用程序不可用 。 这是一个重要的区别,在规划应用程序体系结构时应考虑到这一点。 在这里,了解Angular 依赖注入的工作原理非常重要。
要加载延迟路由,主AppModule
定义了前往该功能NgModule的路径。 为此,您必须为新路由更新根路由器配置。 此示例显示了如何通过为其指定path
和loadChildren
属性来定义延迟加载的路由:
const routes: Routes = [
{
path: 'forums',
loadChildren: 'app/forums/forums.module#ForumsModule'
},
{
path: '',
component: HomeComponent
}
];
loadChildren
属性的语法是一个字符串,该字符串具有NgModule文件的路径(不带文件扩展名), #
符号以及NgModule类的名称: loadChildren: 'path/to/module#ModuleName
。 Angular使用它来知道在运行时在哪里加载文件,并知道NgModule的名称。
延迟加载的路由的路径是在路由的根级别定义的,因此延迟加载的NgModule甚至都不知道其路由的路径。 这使它们更具可重用性,并使应用程序知道何时延迟加载该NgModule。 考虑将所有路由定义为相对路径的延迟加载NgModule,通过组合根路由和延迟加载路由来提供完整路径。
例如,如果您访问/
路线在此应用中,它会载入HomeComponent
和ForumsModule
不会被加载。 但是,一旦用户单击链接查看论坛,就会注意到/forums
路径要求加载ForumsModule
,然后下载并从中注册定义的路由。
路由NgModule
Angular的常见模式是使用单独的NgModule承载所有路由。 这样做是为了分离关注点,并且完全是可选的。 当您通过传递--routing
标志创建新模块时,Angular CLI支持自动生成路由NgModule:
ng generate module path/to/module/feature --routing
发生的情况是,您创建了一个独立的NgModule来定义您的路线,然后功能NgModule导入了它。 路由NgModule可能如下所示:
const routes: Routes = [
{ path: '', component: ForumsComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ForumsRoutingModule { }
然后将其导入到您的ForumsModule
如下所示:
@NgModule({
declarations: [
ForumComponent,
ForumsComponent,
],
imports: [
CommonModule,
FormsModule,
ForumsRoutingModule,
],
providers: [
ForumsService
]
})
export class ForumsModule { }
这在很大程度上是首选项,但这是您应该考虑的常见模式。 本质上,这是使用NgModules进行代码分离的另一种方法。
单身人士服务
我们已经看到了一些有关提供程序的问题,除非您仅在根NgModule中提供服务,否则无法保证在NgModules中获得相同的服务实例。 有一种方法可以定义NgModule,以便它只能为根NgModule声明提供程序,而不为所有其他NgModule声明它们。
实际上,Angular路由器就是一个很好的例子。 在根NgModule中定义路由时,可以使用RouterModule.forRoot(routes)
,而在功能NgModule内部,可以使用RouterModule.forChild(routes)
。 对于需要单个服务实例(单例)的可重用库,此模式很常见。 我们可以通过向NgModule中添加两个静态方法来对任何NgModule进行相同操作,如您在此处看到的:
@NgModule({
declarations: [
ForumComponent,
ForumsComponent,
ThreadComponent,
ThreadsComponent
],
imports: [
CommonModule,
FormsModule,
],
exports: [
ForumsComponent
]
})
export class ForumsModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: ForumsModule,
providers: [ForumsService]
};
}
static forChild(): ModuleWithProviders {
return {
ngModule: ForumsModule,
providers: []
};
}
}
然后在我们的AppModule
,使用forRoot()
方法定义导入,该方法将返回带有提供程序的NgModule。 在导入ForumsModule
任何其他NgModule中,您将使用forChild()
方法,因此您无需再次声明提供程序(从而创建一个新实例):
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ForumsModule.forRoot()
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
NgModules用于分组NgModules
您可以将多个其他NgModule组合为一个,以使其更易于导入和重用。 例如,在我从事的Clarity项目中,我们有许多NgModules仅导出其他NgModules。 例如,这是主要的ClarityModule
,它实际上是重新导出包含每个组件的其他各个NgModule:
@NgModule({
exports: [
ClrEmphasisModule, ClrDataModule, ClrIconModule, ClrModalModule, ClrLoadingModule, ClrIfExpandModule, ClrConditionalModule, ClrFocusTrapModule, ClrButtonModule, ClrCodeModule, ClrFormsModule, ClrLayoutModule, ClrPopoverModule, ClrWizardModule
]
})
export class ClarityModule { }
这使一次导入多个NgModule变得容易,但是,这确实使编译器更难知道哪些NgModule用于树摇优化。
摘要
我们在Angular中进行了NgModules的旋风之旅,并介绍了关键用例。 关于NgModules的Angular文档也很深入,如果您遇到困难,建议您查阅FAQ 。
angular开源代码