在 Angular 中调用具有依赖关系的函数
Posted
技术标签:
【中文标题】在 Angular 中调用具有依赖关系的函数【英文标题】:Invoking a function with dependencies in Angular 【发布时间】:2018-07-01 11:33:19 【问题描述】:使用 Angular 5 和 UIRouter 状态路由。我正在根据这个接口使用一个额外的自定义路由状态属性。
interface AugmentedNg2RouteDefinition extends Ng2StateDeclaration
default?: string | ((...args: any[]) => string | Promise<string>);
当我定义一个抽象状态时,我现在也可以向它添加一个default
属性,因此当尝试路由到抽象状态时,默认值应该将它们重定向到配置的默认值 子状态。
从上面的界面可以理解,default
可以定义为以下任意一种:
// relative state name
default: '.child',
// absolute state name
default: 'parent.child',
// function with DI injectables
default: (auth: AuthService, stateService: StateService) =>
if (auth.isAuthenticated)
return '.child';
else
return stateService.target('.login', ... );
// function with DI injectables returning a promise
default: (items: ItemsService) =>
return items
.getTotal()
.then((count) =>
return count > 7
? '.simple'
: '.paged';
);
要真正让default
工作,我必须配置路由转换服务:
@NgModule(
imports: [
...
UIRouterModule.forChild( // or "forRoot"
states: ...
// THIS SHOULD PROCESS "default" PROPERTY ON ABSTRACT STATES
config: (uiRouter: UIRouter, injector: Injector, module: StatesModule) =>
uiRouter.transitionService.onBefore(
// ONLY RUN THIS ON ABSTRACTS WITH "default" SET
to: state => state.abstract === true && !!state.self.default
,
// PROCESS "default" VALUE
transition =>
let to: transition.to();
if (angular.isFunction(to.default))
// OK WE HAVE TO EXECUTE THE FUNCTION WITH INJECTABLES SOMEHOW
else
// this one's simple as "default" is a string
if (to.default[0] === '.')
to.default = to.name + to.default;
return transition.router.stateService.target(to.default);
);
)
]
)
export class SomeFeatureModule
所以问题在于调用default
,当它是一个可能有一些可注入服务/值的函数时......
配置函数的注入器(config: (uiRouter: UIRouter, injector: Injector, module: StatesModule)
)只能用于获取服务实例,不能调用带有可注入参数的函数。
在 AngularJS 中,这将由 $injector.invoke(...)
完成,它会调用函数并注入其参数。
主要问题
当default
被定义为带有可注射的函数时,我应该如何处理它。
【问题讨论】:
问题是default
可以在某些时候起作用,但实际上并非如此。它到底是什么样子的? config 函数的 Injector 只能获取我不能在这里真正使用的注射剂实例 - 这是什么意思?这些实例是什么,哪些是预期的? ***.com/help/mcve 和明确的问题陈述在这里是必要的。
@estus: default
可以是字符串,也可以是返回字符串或字符串承诺的函数。问题是这个函数可能需要由 Angular 的 DI 容器提供的参数。这就是为什么人们会在 AngularJS 中使用 $injector.invoke
,但 .invoke
函数在我的 Angular 5 代码上下文中不可用。你现在明白问题了吗?
Angular 中没有 $injector.invoke,支持 DI 的函数应该定义为提供者。这个函数和这些参数(服务?)是什么真的很重要。您的评论没有解释这一点,config 函数的 Injector 只能获取我在这里无法真正使用的注射剂实例。请使用详细解释您的案例的示例更新问题,因为一般无法解决该问题。
@estus 我知道没有$injector.invoke
。你认为我为什么首先问这个问题?我显然没有正确描述我的问题,因为您不了解 default
state 属性的作用。因此,注入参数(当default
是具有可注入参数的函数时)可能是任何提供信息以决定应选择哪个默认子状态的信息。即一些身份验证服务......对于那些真正仔细阅读它的人来说,我的问题已经足够详细了。这不是一些基本问题。这是一个提前问题。
@estus:我希望我经过严格编辑的问题现在更加清晰易懂。也可能是,我缺少一些知识,所以我不能让我的问题更清楚,因为它是 ATM。看来我错过了什么。
【参考方案1】:
这是您可以通过添加有关解析服务的部分来解决此问题的一种方法。
// THE IMPORTANT PART
config: (uiRouter: UIRouter, injector: Injector, module: StatesModule) =>
uiRouter.transitionService.onBefore(
// HookMatchCriteria
to: state => state.abstract === true && !!state.self.default
,
// TransitionHookFn
transition =>
let to: transition.to();
if (typeof to.default === "string")
return transition.router.stateService.target(to.default);
else if (typeof to.default === "function")
let functionPromise = Promise.resolve(to.default(injector));
return functionPromise.then((toDefault) => transition.router.stateService.target(toDefault));
检查默认值是否为string
的第一部分很明显。
在第二个if
中,我检查参数是否是一个函数,这会自动将其视为 TypeScript 中的函数,因此可以直接调用它。在可以是Promise<string>
或string
的结果上,然后使用Promise.resolve(value) 方法。此方法为两个输入返回一个新的Promise<string>
,因此我们可以将它与对 stateService 的调用链接起来,它将返回一个 Promise<TargetState>
,这是 TransitionHookFn 的有效返回。
为了解决服务,您可以像这样更改界面:
interface AugmentedNg2RouteDefinition extends Ng2StateDeclaration
default?: string | ((injector: Injector, ...args: any[]) => string | Promise<string>);
然后你也总是在调用函数时发送注入器,因为你在 config 方法中有它。如果所有参数都应该是服务,则删除 ...args
并发送注入器。
那么您作为示例提供的函数将如下所示:
// relative state name
default: '.child',
// absolute state name
default: 'parent.child',
// function with DI injectables
default: (injector: Injector) =>
let auth: AuthService = injector.get(AuthService);
let stateService: StateService = injector.get(StateService);
if (auth.isAuthenticated)
return '.child';
else
return stateService.target('.login', ... );
// function with DI injectables returning a promise
default: (injector: Injector) =>
let items: ItemsService = injector.get(ItemsService);
return items
.getTotal()
.then((count) =>
return count > 7
? '.simple'
: '.paged';
);
【讨论】:
您使用了一个简单的default
函数。无参数...不过,这并不是我要解决的问题。至少不是我正在努力解决的示例......假设default
函数有一个服务参数:default: (myService: MyService) => ...
。您现在将如何调用该函数?您必须以某种方式获取可注入服务实例。在 AngularJS 中,只需调用 $injector.invoke()
,然后它会从 DI 容器中注入参数,但在 Angular 5 中,我们没有 invoke
函数...
好的,我明白了,现在没有仔细阅读,所以我没有明白这是主要问题。然后我将更改函数定义以将Injector
也作为参数,然后直接从注入器解析函数实现中的服务。将更新答案,以便您了解我的意思。【参考方案2】:
在 Angular 中没有与 AngularJS $injector.invoke
直接对应的对象,因为可注入函数预计是在设计时定义的 useFactory
提供程序。
AngularJS 中只有一个注入器实例,但 Angular 中有一个层次结构的注入器,这也使事情变得复杂,因为调用函数的注入器上应该存在依赖项。
处理这个问题的惯用方法是定义所有预期作为提供者调用的函数。这意味着一个函数只能使用它定义在(根模块或子模块)上的注入器的实例:
export function fooDefaultStateFactory(baz)
return () => baz.getStateName();
@NgModule(
providers: [
Baz,
provider: fooDefaultStateFactory,
useFactory: fooDefaultStateFactory,
deps: [Baz]
],
...
)
...
// relative state name
default: '.child',
...
// function with DI injectables
default: fooDefaultStateFactory
然后可以从注入器中检索工厂函数作为任何其他依赖项并调用:
transition =>
...
if (typeof to.default === 'string')
...
else if (to.default)
const defaultState = injector.get(to.default);
if (typeof defaultState === 'function')
// possibly returns a promise
Promise.resolve(defaultState()).then(...)
else ...
与任何函数一起使用的 $injector.invoke
的对应项应该大致类似于 Angular 2/4 Class
helper 中构造函数定义的工作方式(在 Angular 5 中已弃用)。不同之处在于Class
接受使用数组或parameters
静态属性注释的构造函数,注释应为数组数组,因为依赖项可能涉及装饰器(Inject
、Optional
等)。
由于装饰器不适用于未注册为提供者的函数,因此该数组应为普通数组,类似于 Angular 中的 AngularJS implicit annotations 或 deps
提供者:
function invoke(injector, fnOrArr)
if (Array.isArray(fnOrArr))
const annotations = [...fnOrArr];
const fn = annotations.pop();
const deps = annotations.map(annotation => injector.get(annotation));
return fn(...deps);
else
return fnOrArr();
可以绑定注入器实例:
const injectorInvoke = invoke.bind(injector);
injectorInvoke([Foo, Bar, (foo: Foo, bar: Bar) => ...]);
并且调用函数的sn-p修改为:
...
if (typeof defaultState === 'function' || Array.isArray(defaultState))
// possibly returns a promise
Promise.resolve(injectorInvoke(defaultState)).then(...)
else ...
...
【讨论】:
以上是关于在 Angular 中调用具有依赖关系的函数的主要内容,如果未能解决你的问题,请参考以下文章
typescript 测试具有依赖关系的管道。 https://medium.com/google-developer-experts/angular-2-testing-guide-a485b6cb