Angular 1 与 Angular 2 中的依赖注入

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

在本文中,我将展示如何在 Angular 2 中实现在 Angular 1 中使用依赖注入的常见场景。

TypeScript 中的代码示例

我用 TypeScript 编写了代码示例,因为我是该语言的忠实粉丝。这并不意味着您在 Angular 2 中构建应用程序时必须使用它。该框架适用于 ES5 和 ES6。

让我们从一个简单的组件开始

让我们从实现一个简单的登录组件开始。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

现在,在 Angular 2 中实现了相同的组件。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

有经验的开发人员知道登录组件与登录服务的耦合是有问题的。很难单独测试这个组件。而且它也降低了它的可重用性。如果我们有两个应用程序,有两个登录服务,我们将无法重用我们的登录组件。

我们可以通过猴子修补系统加载程序来补救它,这样我们就可以替换登录服务,但这不是正确的解决方案。我们的设计存在一个根本问题,DI 可以帮助我们解决这个问题。

使用依赖注入

我们可以通过将 LoginService 的实例注入构造函数而不是直接创建它来解决我们的问题。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

现在,我们需要告诉框架如何创建服务实例。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

好吧,让我们把这个例子移植到 Angular 2。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

与 Angular 1 一样,我们需要告诉框架如何创建服务。在 Angular 2 中,指令和组件装饰器是您配置依赖注入绑定的地方。在此示例中,App 组件使 LoginService 可用于其自身及其所有后代,包括登录组件。登录服务的实例将在 App 组件旁边创建。所以如果多个孩子依赖它,他们都会得到同一个实例。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

我们将这两个问题分开:登录组件现在依赖于一些抽象的登录服务,应用程序组件创建该服务的具体实现。结果,登录组件不再关心它将获得登录服务的什么实现。这意味着我们可以单独测试我们的组件。我们可以在多个应用程序中使用它。

请注意,Angular 1 依赖于字符串来配置依赖注入。 Angular 2 默认使用类型注释,但是当需要更大的灵活性时,有一种方法可以退回到字符串。

使用不同的登录服务

我们可以将我们的应用程序配置为使用登录服务的另一个实现。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

配置登录服务

依赖注入的一大优点是我们不必担心依赖项的依赖性。登录组件依赖于登录服务,但它不需要知道服务本身依赖什么。

假设该服务需要一些配置对象。在 Angular 1 中,可以按如下方式完成:


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

现在,Angular 2 版本:


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

注入组件的元素

通常需要组件与其 DOM 元素进行交互。这是在 Angular 1 中的实现方式:


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

Angular 2 在这里做得更好。它使用相同的依赖注入机制将元素注入到组件的构造函数中。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

注入其他指令

多个指令一起工作也很常见。例如,如果您有选项卡和窗格的实现,则选项卡组件将需要了解窗格组件。

这就是在 Angular 1 中可以做到的。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

我们使用 require 属性来访问 tab 控制器。然后,我们使用 scope 来访问 pane 控制器。最后,我们使用链接功能将两者连接起来。对于这样一个简单的场景,这是相当多的工作。

而且,Angular 2 再一次在这方面做得更好。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

但我们可以做得更好!选项卡组件可以查询窗格,而不是将自己注册到最近的选项卡。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

Query 解决了开发人员在 Angular 1 中实现它时面临的许多问题:

  • 窗格总是有序的。
  • 查询将通知选项卡组件有关更改。
  • Pane 不需要知道 Tab。 Pane 组件更易于测试和重用。

单一API

Angular 1 有几个用于将依赖项注入指令的 API。谁没有被 factory service provider constant value 之间的区别搞糊涂了?有些对象是按位置注入的(例如,元素),有些是按名称注入的(例如,LoginService)。始终提供一些依赖项(例如,链接中的元素),一些必须使用 require 配置,还有一些使用参数名称配置。

Angular 2 为注入服务、指令和元素提供了单一的 API。所有这些都被注入到组件的构造函数中。因此,需要学习的 API 少了很多。而且您的组件更容易测试。

但是它是如何工作的呢?当组件请求一个元素时,它如何知道要注入什么元素?它的工作方式如下:

该框架构建了一个与 DOM 匹配的注入器树。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

匹配的注入器树:


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

由于每个 DOM 元素都有一个注入器,因此框架可以提供上下文或本地信息,例如元素、属性或附近的指令。

这就是依赖解析算法的工作原理。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

因此,如果 Pane 依赖于 Tab ,Angular 将首先检查 pane 元素是否恰好具有 Tab 的实例。如果没有,它将检查父元素。它将重复该过程,直到找到 Tab 的实例或到达根注入器。

您可以指向页面上的任何元素,并通过使用 ngProbe 获取其注入器。您还可以在抛出异常时查看元素的注入器。

我知道这可能看起来有点复杂,但事实是 Angular 1 已经有了类似的机制。您可以使用 require 注入附近的指令。但是这个机制在 Angular 1 中还没有开发,这就是我们不能完全利用它的原因。

Angular 2 将这种机制推向了合乎逻辑的结论。事实证明我们不再需要其他机制了。

高级示例

到目前为止,我们已经查看了在 Angular 1 和 Angular 2 中都有效的示例。现在,我想向您展示一些在 Angular 1 中无法表达的高级示例。

可选依赖项

要将依赖项标记为可选,请使用 Optional 装饰器。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

控制可见性

您可以更具体地说明要从哪里获取依赖项。例如,您可以在同一元素上请求另一个指令。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

或者您可以在同一视图中请求指令。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

你可以在这里读更多关于它的内容。

提供同一服务的两种实现

因为 Angular 1 只有一个注入器对象,所以你不能在同一个应用程序中有两个 LoginService 的实现。在 Angular 2 中,每个元素都有一个注入器,这不是问题。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

SubApp1 下创建的服务和指令将使用 CustomPaymentService1 ,而在 SubApp2 下创建的服务和指令将使用 CustomPaymentService2 ,即使它们都声明对 PaymentService 的依赖。

查看绑定

以下内容使 LoginService 可用于在 App 的公共子项(也称为 light dom 子项)及其视图中注入。


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });

有时,您只是想让绑定在组件的视图中可用。你可以这样做:


 // Angular 1: Basic Implementation of Login Component

class Login { formValue: { login: string, password: string } = {login:'', password:''};

onSubmit() {
    const service = new LoginService();
    service.login(this.formValue);
}

}

module.directive("login", () => { return { restrict: 'E', scope: {}, controller: Login, controllerAs: 'ctrl', template: <form ng-submit="ctrl.onSubmit()"> Text <input type="text" ng-model="ctrl.formValue.login"> Password <input type="password" ng-model="ctrl.formValue.password"><button>Submit</button> </form> }; });


概括

  • 依赖注入是 Angular 2 的核心部分之一。
  • 它允许您依赖于接口,而不是具体类型。
  • 这导致更多的解耦代码。
  • 这提高了可测试性。
  • Angular 2 有一个 API 用于将依赖项注入组件。
  • Angular 2 中的依赖注入更强大。

阅读更多