本文深入探讨AngularJS中依赖注入的高级特性,重点解析如何利用$injector服务实现延迟加载、生命周期管理及延迟处理。通过具体代码示例,展示如何拦截内置服务、创建组件多实例以及动态解析依赖,帮助开发者突破常规工厂和服务的限制,构建更灵活的企业级应用架构。
AngularJS高级控制反转容器概述
本节将深入探讨几种常用的高级控制反转(IoC)容器技术,主要涵盖延迟加载(Lazy-Loading)、生命周期管理(Lifetime Management)以及延迟创建与解析(Deferred Creation/Resolution)等核心概念。
延迟加载机制与实现
延迟加载指的是仅在真正需要使用对象时才进行实例化的机制。虽然许多依赖注入框架倾向于在应用启动阶段就预先创建所有组件及其依赖项,但在某些场景下,直到应用逻辑真正执行到相关部分时,才希望实例化这些组件。在Angular中,一个典型的例子是:在配置阶段设置某个行为,而该行为又引用了尚未创建的组件。
假设我们需要拦截系统内置的$log服务,并将其存储在$rootScope中(尽管这种做法并不推荐,但作为示例足够直观)。为了实现拦截,我们在配置阶段使用$provide调用装饰器方法。如果此时直接引用$rootScope,可能会因为循环引用导致异常。解决这一问题的方案是利用$injector进行延迟加载。
以下代码片段展示了仅在$rootScope首次被访问时才执行加载的逻辑。
$provide.decorator(, [, ,
($delegate, $injector) {
log = $delegate.log.bind($delegate);
$delegate.log = (msg) {
rs = $injector.get();
(rs.logs === undefined) {
rs.logs = [];
}
rs.logs.push(msg);
log(msg);
};
$delegate;
}]);
后续的所有调用都将返回相同的单例$rootScope实例。这里提供了一个可运行的示例。此前曾有一种(错误的)观点认为Angular仅支持单例模式,这显然是不准确的。$injector中提供的方法正是用于管理组件生命周期的关键工具。
组件生命周期管理
生命周期管理
生命周期管理涉及如何控制组件实例的创建与复用。默认情况下,当注入一个Angular依赖时,依赖注入系统会自动创建该组件的一个副本,并在整个应用中复用此实例。在大多数场景下,这种单例行为符合预期。然而,某些特定需求要求同一组件存在多个独立实例。例如,考虑以下计数器服务:
Counter($log) {
$log.log();
}
angular.extend(Counter.prototype, {
count: 0,
increment: () {
.count += 1;
.count;
}
});
Counter.$inject = [];
app.service(, Counter);
如果你的应用需要跟踪多个不同的计数器,直接注入该服务会导致所有地方共享同一个计数器状态。这是否意味着Angular存在限制?
并非如此。再次强调,通过$injector服务,你可以在任何时刻创建该组件的新副本。以下代码展示了如何创建两个独立的计数器实例:
app.run([, , ,
(rs, c, i) {
rs.count = c.count;
rs.update = c.increment;
rs.update2 = () {
c = i.instantiate(Counter);
rs.count2 = c.count;
rs.update2 = () {
c.increment();
rs.count2 = c.count;
};
};
}]);
从代码中可以看出,每个计数器都由独立的实例进行跟踪,此处提供了完整的示例代码。如果你需要频繁生成新实例,可以通过以下方式注册服务:
app.factory(, [,
(i) {
{
getCounter: () {
i.instantiate(Counter);
}
};
}]);
生成所需实例的过程非常简便,你也可以使用工厂组件来替代直接使用$injector:
app.run([, ,
(rs, cf) {
c1 = cf.getCounter(),
c2 = cf.getCounter();
rs.count = c1.count;
rs.update = c1.increment;
rs.count2 = c2.count;
rs.update2 = () {
rs.count2 = c2.increment();
};
}]);
此处提供了完整版本的示例代码。由此可见,利用Angular内置的依赖注入机制完全可以实现对组件生命周期的精细管理。那么,延迟处理(Deferred Resolution)又是如何运作的呢?例如,有些组件需要在Angular配置完成后引入,并且需要用其依赖项进行包装。
延迟处理与依赖解析
延迟处理(Deferred Resolution)
前文已介绍了一种在Angular中延迟处理依赖的方法。当需要包装某些逻辑时,可以调用$injector服务的instantiate方法。该方法可以通过参数探测来解析依赖,其表现类似于使用$inject的静态属性,或者通过检查传入的数组来实现。也就是说,以下写法是完全有效的:
$injector.instantiate(['dependency', Constructor]);
此外,还可以调用带有装饰数组参数的方法。假设有一个方法依赖于$log服务,你可以在运行时通过延迟处理的方式调用它,如下所示:
myFunc = [, ($log) {
$log.log();
}];
$injector.invoke(myFunc);
总结与Angular外部的注入应用
你可以查看此处的示例代码(打开浏览器控制台,观察按下按钮后的输出结果)。 总结
综上所述,Angular的依赖注入系统提供了丰富的高级特性,这些特性在商业级应用开发中既常见又必要。虽然factories、services和providers提供了便捷的开发体验,但这容易让Angular开发者产生一种错觉,认为这是唯一的依赖管理方式。其核心奥秘在于$injector服务:利用它,你可以生成所需的单例、创建组件的新实例,或者动态引用带有依赖关系的方法。
最后需要指出的是,客户端代码中的注入机制即使在Angular框架之外也是可用的。例如,以下代码展示了如何在Angular外部通过注入调用$log服务,详见此处链接。为什么在方法的数组参数中传入了'ng'?因为'ng'是Angular的核心模块,在包装模块时会被隐式添加。但是,如果你的指令需要生成自己的注入实例,则必须显式地添加该模块。