TreeShakeableProvidersandServicesinAngularAngular最近引入了一个新功能,TreeShakeableProviders。TreeShakeableProviders是一种定义服务和Angular依赖注入系统使用的其他东西的方法,可以提高Angular应用程序的性能。首先,在我们深入挖掘之前,让我们定义treeshaking。Treeshaking是构建过程中的一个步骤,它从代码库中删除未使用的代码。删除未使用的代码可以被认为是“摇树”,或者您可以想象一棵树的物理摇动以及剩余的枯叶从树上掉落。通过使用treeshaking,我们可以确保我们的应用程序只包含我们的应用程序需要运行的代码。例如,假设我们有一个包含函数a()、b()和c()的实用程序库。在我们的应用程序中,我们导入并使用函数a()和c()而不是b()。我们不希望b()的代码被捆绑和部署给我们的用户。Treeshaking是从我们发送到用户浏览器的已部署生产代码中删除函数b()的机制。为什么不能在以前版本的Angular中对服务进行tree-shaking?这实际上可以追溯到我们在早期版本的Angular中注册服务的方式。让我们看一个示例,说明如何在以前的Angular版本中注册依赖注入服务。从'@angular/core'导入{NgModule};从'@angular/platform-b??rowser'导入{BrowserModule};从'./app.component'导入{AppComponent};从'./shared.service导入{SharedService}';@NgModule({imports:[BrowserModule,FormsModule],declarations:[AppComponent],bootstrap:[AppComponent],providers:[SharedService]})exportclassAppModule{}如你所见,我们导入了服务并将其放入我们的AngularAppModule。这会将服务注册到Angular的依赖注入系统。每当组件请求使用此服务时,Angular的DI将确保创建服务和任何依赖项并将其传递给组件的构造函数。这个注册系统的问题是构建工具和编译器很难确定这段代码是否在我们的应用程序中使用。tree-shaking系统删除代码的主要方式之一是查看我们定义的导入路径。如果一个类或函数没有被导入,它就不会包含在我们提供给用户的生产代码包中。如果它是导入的,摇树器会假定它正在应用程序中使用。在我们上面的示例中,我们在AppModule中导入并引用了我们的服务,导致显式依赖关系不会被摇树移除。AngularTreeShakingProviders使用TreeShakingProviders(TSP),我们可以使用不同的机制来注册我们的服务。使用这种新的TSP机制将提供tree-shaking性能和依赖注入的好处。我们有一个带有特定代码的演示应用程序来演示我们如何注册这些服务的不同性能特征。让我们看看新的TSP语法是什么样的。从'@angular/core'导入{Injectable};@Injectable({providedIn:'root'})exportclassSharedService{constructor(){}}在@Injectable装饰器中,我们有一个名为providedIn的新属性。有了这个属性,我们就可以告诉Angular向哪个模块注册我们的服务,而无需导入模块并将其注册到NgModule提供者。也就是说,不需要像旧版Angular那样在AppModule中显式导入服务,并将其添加到NgModule的providers数组中。默认情况下,此语法将其注册到根注入器,这将使我们的服务成为应用程序范围的单例。对于大多数用例,根提供程序是一个合理的默认值。如果你仍然需要控制服务实例的数量,Angular模块和组件上的普通提供者API仍然可用。使用这个新的API,您可以看到,由于我们不必将服务导入NgModule来注册它,因此我们没有显式的依赖关系。因为没有import语句,构建工具可以确保服务仅在组件使用时才捆绑在我们的应用程序中。让我们看一个示例应用程序。从'@angular/core'导入{NgModule};从'@angular/platform-b??rowser'导入{BrowserModule};从'@angular/forms'导入{FormsModule};从'@angular/router'导入{RouterModule};import{AppComponent}from'./app.component';import{HelloComponent}from'./hello.component';import{Shared3Service}from'./shared3.service';@NgModule({imports:[BrowserModule,FormsModule,RouterModule.forRoot([{path:'',component:HelloComponent},{path:'feature-1',loadChildren:()=>import('./feature-1/feature-1.module').then(m=>m.Feature1Module)},{路径:'feature-2',loadChildren:()=>import('./feature-2/feature-2.module').then(m=>m.Feature2Module)}}])],declarations:[AppComponent,HelloComponent],bootstrap:[AppComponent],providers:[Shared3Service]})exportclassAppModule{}在这个示例应用程序中,我们有三个组件;两个是懒加载模块,一个是我们的着陆页组件。我们还将在应用程序中使用三种不同的服务。让我们从第一个服务开始,看看它是如何使用的。import{Injectable}from'@angular/core';console.log('SharedServicebundled因为两个组件使用它');@Injectable({providedIn:'root'})exportclassSharedService{constructor(){console.log('SharedService实例化');我们的第一个服务使用treeshakableprovidersAPI。我们在每个延迟加载的功能模块中两次导入此服务,如下所示。从'@angular/core'导入{Component,OnInit};从'./../shared.service'导入{SharedService};@Component({选择器:'app-feature-1',templateUrl:'./feature-1.component.html',styleUrls:['./feature-1.component.css']})exportclassFeature1ComponentimplementsOnInit{constructor(privatesharedService:SharedService){}ngOnInit(){}}因为我们的两个服务1在每个组件中使用,因此代码被加载并捆绑到我们的应用程序中。如果我们检查控制台,我们会看到以下消息:SharedServicebundledbecausetwocomponentsuseitSecondservice:import{Injectable}from'@angular/core';console.log('Shared2Service没有被捆绑,因为它没有被使用');@Injectable({providedIn:'root'})exportclassShared2Service{constructor(){}}如果我们检查控制台,我们看不到日志消息。这是因为我们的任何功能模块或组件中都没有使用此服务。由于未使用,因此不会捆绑和加载任何代码。最后,我们的第三个服务类似于前两个看起来像这样:){}}控制台信息:Shared3Service尽管未使用但已捆绑因为Shared3Service是使用较旧的提供程序API注册的,因此由于需要注册的导入语句,它会创建显式依赖项。import语句将导致构建系统包含并加载此代码,即使没有组件使用它也是如此。在这三个服务之间,我们可以看到tree-shaking系统如何在我们的应用程序中包含或删除代码的特征。使用TSPAPI,我们的服务仍然是单例的,即使对于我们示例中延迟加载模块中使用的服务也是如此。如果我们加载示例应用程序,我们会注意到,如果我们在功能一和功能二之间进行路由,SharedService中的控制台日志只会被调用一次。一旦一个模块被请求,Angular将实例化它并确保该实例用于应用程序生命周期的剩余部分。AngularTreeShakeableProviders为我们的应用程序提供了更好的性能,并减少了创建可注入服务所需的样板代码量。
