处理非平凡的AngularJS应用程序初始化要求的最佳实践?

时间:2013-05-01 12:31:07

标签: angularjs angularjs-scope

我有一个应用程序,它有一些特定的(非平凡的)初始化要求,并不清楚最佳实践解决方案是什么。抱歉,文字墙。问题本身并不复杂,但我需要确保我的推理是明确的。

首先,应用程序本身:

  • 它具有用户身份验证,但仅在两个时间点强制执行:
    1. 第一次加载应用程序(第一次)。我将通过问题的其余部分来称呼这个要求(1)。
    2. 在与服务器端交互时的需要基础上。这部分我已经解决了与http://ngmodules.org/modules/http-auth-interceptor类似的东西,虽然是一个自定义解决方案(这是必需的,因为应用程序需要使用一些我不希望与Angular相关的服务)。我将在剩下的问题中将此要求(2)称为。
  • 有两个与此问题相关的控制器:
    • 导航栏控制器(已修复,未绑定到视图)。
    • 控制器应用于所使用的视图(ng-view)。
  • 使用angular.bootstrap手动启动。

这个问题与用户身份验证处理有关。用户必须在需要的基础上进行身份验证的要求(2)已经解决。它目前处理如下:

  1. 某个服务器端请求由我的一个Angular服务模块执行。如果应用的身份验证令牌已过期(或不存在于一起),则该请求可能会导致401响应。
  2. 提出请求的应用程序服务模块发现401响应并应用$ rootScope。$ broadcast('app:auth')。
  3. 使用$ scope。$ on('app:auth')的某些代码获取身份验证广播,显示模式身份验证对话框,然后确保解析/拒绝原始服务请求承诺(如果用户拒绝)在对话框中按取消。)
  4. 要求(1)和(2)之间的唯一区别是(1)应该是强制认证对话框(用户不能简单地用'取消'或'esc'按钮拒绝它)并且(1)应该尽可能早地在应用程序初始化中发生。

    现在,我的问题是要求(1),真的和Angular最佳实践。我可以看到有几种方法可以做到这一点:

    1. 完全在Angular之外执行此一次性身份验证。这里的缺点显然是我必须为模态对话框和初始化编写基本上重复的逻辑。其中一些可以共享,但不是全部。

    2. 在应用程序的某个特殊(固定)控制器(如导航栏控制器)中执行此一次性身份验证。

    3. 在angular.module.run中执行此一次性身份验证。

    4. 这里的目的显然是在用户(或应用程序)触发应用程序中的其他内容之前“强制”对用户进行身份验证。

      我很想使用数字(3),因为我可以重新使用已经被需求(1)使用的所有代码。但是,您会遇到将事件监听代码放在何处的问题。此时尚未启动任何控制器/部分应用程序(仅完成注射)。

      如果我将认证事件的逻辑放在应用程序控制器中,那么该控制器甚至不会在那时启动,因此无法注册该事件。如果我将$ rootScope。$ broadcast放在$ timeout内并延迟0,我的导航栏控制器已启动,但不是我的视图控制器。如果我将$ rootScope。$ broadcast放在$ timeout内并延迟100毫秒,我的控制器都已启动(在 MY 计算机上)。

      问题显然是我需要使用的延迟量取决于计算机以及事件处理程序代码的确切范围。它也可能完全取决于Angular初始化控制器发现的顺序DOM。

      (3)的替代版本也可能是在angular.module.run中进行$ rootScope。$ broadcast,并将事件监听器附加到$ rootScope本身。我倾向于这是最直接的方式。

      请参阅以下plunker(仅尝试突出时间问题):http://plnkr.co/edit/S9q6IwnT4AhwTG7UauZk

      所有这些归结为以下最佳实践问题,真的:

      应该在哪里放置应用程序范围的代码和非平凡的应用程序初始化代码?我应该将$ rootScope视为实际的“应用程序”吗?

      谢谢!

2 个答案:

答案 0 :(得分:1)

答案简短:

应用程序范围的代码应该在服务中。

应用程序初始化代码应位于run块中。

更长的答案:

应该在服务中定义像身份验证这样的应用程序范围的代码。此服务应公开API,您的应用程序的其余部分可以与之交互以实现该任务。当然,服务的工作是隐藏实现细节。服务本身应该处理从(最初)获取身份验证信息的位置 - 可能来自cookie,可能来自本地存储或会话存储......或者甚至可能进行http调用。但所有这些都被封装到了身份验证服务中。

因为现在您已经编写了一个单独的服务,并且可以inject填入您的运行块,您就可以了。你真的不需要$rootScope$rootScope是另一个注入的服务。但是因为它参与了脏检查机制,而且看起来这个服务不需要......你不需要用额外的任务来过度负担$ rootScope。它不是它的工作,也许它可以委托给其他唯一的任务是身份验证的服务。因为你的服务也是一个单身人士,所以维持状态也是惊人的。您也许可以设置一个标志,例如isAuthenticated,如果需要可以在以后检查。

哦,你的模态之间也应该是一个服务..如果你还没有,请参阅Angular UI中的$ dialog服务。这意味着身份验证可以直接使用$dialog服务。

答案 1 :(得分:0)

您应该将应用程序范围的非平凡初始化代码放在提供程序中。提供程序在初始化方面提供了最大的灵活性,因为它们可以在$ injector实际创建服务实例之前用于配置服务。

app.provider('service', function() {

    // add method to configure your service
   this.configureService = function() { ... };

   this.$get = function (/*injectibles*/) {
        // return the service instance
        return {...}; 
   };

});

配置块是您初始化提供商的机会。将您的提供商注入您的配置功能(注意所需的'提供商后缀)并执行设置提供商所需的任何初始化代码。请记住,提供者不是服务 - $ inject将使用它来创建您的服务。

app.config(function(serviceProvider) { 

    serviceProvider.configureService();
    serviceProvider.setTimeout(1000);
    serviceProvider.setVersion('1.0);
    serviceProvider.setExternalWebService('api/test');

    ... more configuration ...
};

提供程序和配置块适合初始化有几个原因:

  1. 配置块仅在应用程序生命周期的早期调用一次
  2. 提供程序是可配置的 - 这意味着您可以在实际创建服务之前初始化提供程序。
  3. 配置块的主要用途是初始化。它支持注入提供程序作为执行初始化的机会。
  4. 提供者是单身人士(如工厂和服务) - 意味着一个服务实例由$ injector创建,然后在所有控制器,指令等之间共享 - 基本上是服务注入的任何地方。
  5. 现在对于要求(1)和(2) - 我认为你正走在正确的轨道上。我建议创建一个authLogin指令,根据" IsAuthenticated"显示或隐藏模态登录对话框。在示波器上观看的属性。这将满足在应用程序启动时显示登录模式对话框的要求。用户成功进行身份验证后,将IsAuthenticated属性设置为true(这将隐藏对话框)。

    第二个要求是通过HTTP拦截器处理的。当发出请求并且用户未经过身份验证时,该服务会将事件从$ rootScope向下广播到子范围。您可以使用相同的authLogin指令侦听事件,并通过将IsAuthenticated属性设置为false来处理它。由于IsAuthenticated是一个监视属性,它将触发模态登录对话框,以便用户可以再次登录。

    您可以通过多种方式实施要求(1)和(2)。我对你的方法略有不同,但总的来说它是相同的方法。