在 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&lt;string&gt;string 的结果上,然后使用Promise.resolve(value) 方法。此方法为两个输入返回一个新的Promise&lt;string&gt;,因此我们可以将它与对 stateService 的调用链接起来,它将返回一个 Promise&lt;TargetState&gt;,这是 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) =&gt; ... 。您现在将如何调用该函数?您必须以某种方式获取可注入服务实例。在 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 静态属性注释的构造函数,注释应为数组数组,因为依赖项可能涉及装饰器(InjectOptional 等)。

由于装饰器不适用于未注册为提供者的函数,因此该数组应为普通数组,类似于 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 中调用具有依赖关系的函数的主要内容,如果未能解决你的问题,请参考以下文章

Spring04-----DI

具有依赖关系的多个调用 redux-thunk

typescript 测试具有依赖关系的管道。 https://medium.com/google-developer-experts/angular-2-testing-guide-a485b6cb

我能否在C中创建具有未解决的依赖关系的函数?

C++ 加载一个具有依赖关系的共享对象并访问它们的功能

在 CMake 中具有依赖关系的自定义对象