3

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

首先,应用程序本身:

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

这个问题是关于用户身份验证处理的。要求 (2) 已经解决了用户必须根据需要进行身份验证的情况。目前处理如下:

  1. 一些服务器端请求由我的 Angular 服务模块之一执行。如果应用的身份验证令牌已过期(或根本不存在),则该请求可能会导致 401 响应。
  2. 发出请求的应用程序服务模块发现 401 响应并应用 $rootScope.$broadcast('app:auth')。
  3. 身份验证广播由使用 $scope.$on('app:auth') 的某些代码获取,显示模式身份验证对话框,然后确保原始服务请求承诺已解决/拒绝(如果用户按下取消则拒绝对话)。

要求 (1) 和 (2) 之间的唯一区别是 (1) 应该是强制身份验证对话框(用户不能简单地使用“取消”或“esc”按钮拒绝它)并且 (1) 应该尽早发生尽可能在应用程序初始化中。

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

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

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

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

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

我很想使用数字 (3),因为这样我就可以重用所有已按要求 (1) 使用的代码。但是,您随后会遇到将事件侦听代码放置在何处的问题。此时尚未启动应用程序的任何控制器/部分(只有注入完成)。

如果我将身份验证事件的逻辑放在应用程序控制器中,则该控制器甚至不会在那时启动,因此将无法注册该事件。如果我将 $rootScope.$broadcast 放置在延迟为 0 的 $timeout 中,我的导航栏控制器已经启动,但不是我的视图绑定控制器。如果我将 $rootScope.$broadcast 放置在延迟为 100 毫秒的 $timeout 中,则我的两个控制器都已启动(在的计算机上)。

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

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

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

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

应用程序范围的代码和重要的应用程序初始化代码应该真正放在哪里?我应该将 $rootScope 视为实际的“应用程序”吗?

谢谢!

4

2 回答 2

1

简短的回答:

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

应用程序初始化代码应该在run块中。

更长的答案:

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

因为现在您已经编写了一个单独的服务,并且您可以将inject内容放入您的运行块中,您就可以开始使用了。你真的不需要$rootScope. 这$rootScope是另一个注入服务。但是因为它参与了脏检查机制,并且看起来这个服务不需要......你不需要为这个额外的任务增加 $rootScope 的负担。它不是它的工作,也许它可以委托给其他一些唯一任务是身份验证的服务。因为你的服务也是一个单例,所以它在维护状态方面也很棒。您也许可以设置一个标志,isAuthenticated如果需要,可以稍后检查。

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

于 2013-05-01T13:14:17.393 回答
0

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

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

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

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

});

配置块是您初始化提供程序的机会。将您的提供者注入您的配置函数(注意所需的“提供者”后缀)并执行设置提供者所需的任何初始化代码。记住,提供者不是服务——它是 $injector 用来创建你的服务的东西。

app.config(function(serviceProvider) { 

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

    ... more configuration ...
};

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

  1. 配置块在应用程序生命周期的早期只被调用一次
  2. 提供者是可配置的——这意味着您可以在实际创建服务之前初始化提供者。
  3. config 块的主要目的初始化。它支持注入提供者作为执行初始化的机会。
  4. 提供者是单例(如工厂和服务)——这意味着一个服务实例由 $injector 创建,然后在所有控制器、指令等之间共享——基本上是注入服务的任何地方。

现在对于要求(1)和(2) - 我认为你在正确的轨道上。我建议创建一个 authLogin 指令,该指令显示或隐藏基于范围内正在监视的“IsAuthenticated”属性的模式登录对话框。这将满足在应用程序启动时显示登录模式对话框的要求。用户成功验证后,将 IsAuthenticated 属性设置为 true(然后隐藏对话框)。

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

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

于 2014-06-14T10:11:43.613 回答