在Angular中(不仅限于)管理模块的方法

开发人员在Angular中编写他的第一个项目时,并不能立即了解与您合作的实体的组织。


您可能会遇到的问题之一是Angular模块的使用效率低下,特别是重载的app模块:他们创建了一个新组件,将其放入其中,服务也在那里。 一切似乎都很棒,一切正常。 但是,随着时间的流逝,这样的项目将变得难以维护和优化。


幸运的是,Angular为开发人员提供了创建自己的模块的能力,并且也将其称为功能模块。




域功能模块


重载的应用模块需要拆分。 因此,要做的第一件事是在应用程序中选择大块并将其放置在单独的模块中。


一种流行的方法是将应用程序拆分为域功能模块。 它们旨在根据接口的每个部分执行的关键任务(域)划分接口。 域功能模块的示例包括配置文件编辑页面,产品页面等。 简而言之,所有这些都可以在菜单项下。


图片

蓝框内的所有广告以及其他菜单项的内容均应拥有其自己的域功能模块。


域功能模块可以使用不限数量的可声明内容(组件,指令,管道),但是它们仅导出表示该模块UI的组件。 域功能模块通常是在一个较大的模块中导入的。


域功能模块通常不会在其内部声明服务。 但是,如果它们宣布,则这些服务的寿命应限于模块的寿命。 这可以通过在模块的外部组件中使用延迟加载或广告服务来实现。 这些方法将在本文后面讨论。


延迟加载


将应用程序划分为Domain Feature模块可以使您使用延迟加载 。 因此,您可以从原始捆绑软件中删除首次打开应用程序时用户不需要的内容:用户个人资料,产品页面,照片页面等。 所有这些都可以按需加载。


服务和喷油器


该应用程序分为大块-模块,其中一些模块按需加载。 问题:应该在哪里宣布全球服务? 如果我们想限制服务范围呢?


延迟加载的模块的注入器


与声明式不同,声明式的存在必须在使用它们的每个模块中声明,在任何模块中一次声明的服务单调在整个应用程序中变得可用。


事实证明,可以在任何模块中声明服务,不用担心吗? 不是那样的


如果应用程序仅使用全局注入器,则上述内容是正确的,但是通常所有事情都更加有趣。 延迟加载的模块具有自己的注入器(也有组件,但稍后会介绍更多组件)。 为什么延迟加载的模块甚至会创建自己的注入器? 原因在于依赖注入在Angular中的工作方式。


在开始使用之前,可以向注射器补充新的供应商。 一旦注入程序创建了第一个服务, 它将关闭以添加新的提供程序。


当应用程序启动时,Angular首先设置根注入器,在其中修复在App模块和导入到其中的模块中声明的那些提供程序。 这甚至在创建第一个组件之前以及在为它们提供依赖项之前就可以实现。


在模块延迟加载的情况下,全局注射器已配置很长时间并已投入运行。 加载的模块别无选择,只能创建自己的注射器。 该注射器是启动负载的模块中使用的注射器的子代。 这导致原型链中的javascript开发人员熟悉的行为:如果在延迟加载的模块的注入器中找不到该服务,则DI框架将在父注入器中寻找它,依此类推。


因此,延迟加载的模块允许您声明仅在该模块的框架内可用的服务。 就像javascript原型一样,提供者也可以重新定义。


返回到域功能模块,所描述的行为是一种限制在其中进行广告发布的提供程序的寿命的方法。


核心模块


因此,全球服务应在哪里宣布,例如授权服务,API服务,用户服务等? 简单的答案在App模块中。 但是,为了恢复App模块中的顺序(这是我们正在做的事情),您应该在一个称为Core模块的单独模块中声明全局服务,并将其仅导入App模块。 结果将与直接在App模块中声明服务的结果相同。


从版本6开始,有一个机会可以声明全局服务而不将其导入任何地方。 您需要做的就是将providerIn选项添加到Injectable并在其中指定值“ root”。 以这种方式声明的服务可用于整个应用程序,因此无需在模块中声明它们。


除了这种方法可以在没有模块的情况下探索角度的光明前景之外,它还有助于截断不必要的代码。


单例测试


但是,如果项目中的某人想要将Core模块导入其他地方怎么办? 可以保护自己免受此伤害吗? 可以的


向Core模块添加一个构造函数,要求您将Core模块注入其中(您自己就是对的),并使用Optional和SkipSelf装饰器标记此声明。 如果注入器将依赖项放入变量中,则有人试图重新声明Core模块。


在BrowserModule中使用方法

使用BrowserModule中描述的方法。


此方法可以与模块和服务一起使用。


组件中的服务公告


我们已经考虑过一种使用延迟加载来限制提供程序范围的方法,但这是另一种方法。


每个组件实例都有其自己的注入器,并对其进行配置,就像NgModule装饰器一样,Component装饰器也具有providers属性。 而且-viewProviders的附加属性。 它们两者都用于配置喷射器组件,但是,每种方法中声明的提供者具有不同的范围。


要了解差异,您需要简短的背景知识。


组件由视图和内容组成。


我扭曲组件

查看组件


我满足组件

内容成分


组件html文件中的所有内容都是其视图,而在打开和关闭组件标签之间传递的是其内容。


获得的结果:


结果

结果


因此,添加到提供者的提供者在声明它们的组件的视图以及传递给组件的内容中都是可用的。 顾名思义,viewProviders使服务仅在查看和关闭内容时可见。


尽管最佳实践是在根注入器中声明服务,但是在某些情况下,使用注入器组件很容易:


第一个是每个新组件实例必须具有其自己的服务实例时。 例如,一种存储特定于组件的每个新实例的数据的服务。


对于另一种情况,我们需要记住,尽管域功能模块可以声明仅需要的某些提供程序,但还是希望这些提供程序与这些模块一起消失。 在这种情况下,我们将在最外部的组件(从模块导出的组件)中声明提供程序。


例如,负责用户配置文件的域功能模块。 我们将在最外部的组件UserProfileComponent的提供程序中声明仅对应用程序的这一部分必需的服务。 现在,在此组件的标记中声明的所有可声明的内容以及在内容中传递给它的所有可声明的内容将接收相同的服务实例。


可重复使用的组件


与我们要重用的组件做什么? 这个问题也没有确切的答案,但是有行之有效的方法。


共享模块


项目中使用的所有组件都可以存储在一个模块中,从中导出它们,然后将其导入到项目中可能需要这些组件的模块中。


在这样的模块中,您可以放置​​按钮,下拉列表,样式化的文本块等组件以及自定义指令和管道。


这样的模块通常称为SharedModule。


重要的是要注意,SharedModule不应声明服务。 或使用forRoot方法声明。 我们待会儿再谈他。


尽管SharedModules方法可行,但有两点:


  1. 我们并没有使应用程序的结构更整洁,我们只是将混乱从一个地方转移到了另一个地方。
  2. 这种方法没有考虑到Angular的光明前景,因为Angular没有模块。

避免这些缺点的方法是并且涉及为每个组件创建模块。


每个组件的模块数或SCAM(单个组件的角度模块)


创建新组件时,应将其放在自己的模块中。 您必须将组件依赖项导入同一模块。



图片

每当在应用程序中的任何位置需要某个组件时,所需要做的就是导入该组件的模块。


用英语来说,这种方法称为每个组件模块或SCAM-单组件角度模块。 尽管名称包含单词component,但是这种方法也适用于管道和指令(SPAM,SDAM)。


这种方法的最大优势可能是简化了组件测试。 由于为组件创建的模块将导出该模块,并且已经包含了它所需的所有依赖关系,因此配置TestBed只需将其放入导入中即可。


这种方法有助于项目代码中的顺序和结构,并且也为我们提供了将来无需模块的准备,在该模块中,如果要在另一个组件的标记中使用一个组件,则只需要在Component指令中声明依赖项即可。 您可以通过本文对未来有所了解。


ModuleWithProviders接口


如果在项目中启动了一个包含XYZ服务声明的模块,并且随着时间的流逝该模块开始在各处使用,那么该模块的每次导入都会尝试将XYZ服务添加到相应的注入器中,这不可避免地会导致冲突。 Angular针对这种情况有一套规则 ,可能与开发人员的期望不符。 对于进样器延迟加载的模块尤其如此。


为了避免冲突问题,Angular提供了ModuleWithProviders接口,该接口允许您将提供程序附加到模块上,同时不影响模块本身的提供程序。 而这正是上述情况下所需要的。


策略forRoot(),forChild()


为了将服务精确地固定在全局注入器中,带有提供程序的模块仅导入AppModule中。 从导入的模块的侧面,您只需要创建一个返回ModuleWithProviders的静态方法,该方法在历史上的名称为forRoot。


图片


返回ModuleWithProviders的方法可以是任意数字,并且可以根据需要命名。 forRoot是比要求更方便的约定。


例如,RouterModule具有静态的forChild方法,该方法用于在延迟加载的模块中配置路由。


结论:


  1. 通过关键任务将用户界面分开,并为每个选定的部分创建您自己的模块:除了更方便地理解项目代码的结构之外,还有机会懒惰地加载界面的各个部分
  2. 如果应用程序体系结构需要,请使用延迟加载的模块和组件的注入器
  3. 将全球服务公告发布在单独的模块Core模块中,并将其仅导入到app模块中。 这将有助于清洁应用程序模块。
  4. 更好的方法是,将providerIn选项与Injectable装饰器的根值一起使用。
  5. 将hack与Optional和SkipSelf装饰器结合使用,以防止重新导入模块和服务
  6. 将可重用的组件,指令和管道存储在共享模块中
  7. 但是,最好的方法也是对未来的展望,它有助于测试,它是为每个组件(指令和管道)创建一个模块。
  8. 如果要避免提供程序冲突,请使用ModuleWithProviders接口。 一种流行的方法是实现forRoot方法以将提供程序添加到根模块

Source: https://habr.com/ru/post/zh-CN467525/


All Articles