在本文中,我将展示如何在 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 中的依赖注入更强大。
阅读更多