Angular:$ rootScope变量vs事件

时间:2015-04-21 14:17:03

标签: javascript angularjs

我正在处理管理用户登录的应用。与许多应用程序一样,我想在用户登录时更改标题。 我使用index.html包含ng-include

的主文件header.html

我找到了两个解决方案(我对角度来说很新,所以两者都可能出错):

1)使用$ rootScope.broadcast()

因此,当用户登录I广播(auth.js,它在factory内)时,控制器会在标题中拦截该消息。

auth.js

$rootScope.$broadcast('logged',user);

controller.js

$scope.$on('logged', function(evnt, message){
      $scope.user = message;
    });

header.html

<div class="header" ng-controller="GcUserCtrl as gcUserCtrl">
...
   <li><a ng-show="user" href="#">User: {{user.name}}</a></li>

2)设置$ rootScope变量

据我所知$rootScope是所有范围的根(命名很聪明),所有$scope都可以访问它。

auth.js

$rootScope.user=user;

heaeder.html(这里不需要控制器)

<div class="header">
...
   <li><a ng-show="user" href="#">User: {{user.name}}</a></li>

现在,处理它的正确方法是什么?

  • 第一个似乎有点昂贵,因为广播可能需要做很多检查。
  • 第二个..好吧,我不是全球变量的粉丝..

修改

3)使用服务

在alex的评论之后我添加了这个选项,即使我无法使其正常工作。 (here the plunkr没有事件就无法运作

index.html

...
 <ng-include src="'header.html'"></ng-include>
...

header.html

数字1)

controller.js

.controller('GcUserCtrl', ['$scope','my.auth','$log', function ($scope, auth, $log) {
    $scope.user = auth.currentUser();
  }]);

my.auth.js

.factory('my.auth', ['$rootScope', '$log', function ($rootScope, $log, localStorageService) {
    var currentUser = undefined;

    return {

      login: function (user) {
        currentUser = user;
       ...

      },

  ...
      currentUser: function () {
        return currentUser;
      }
    };
  }]);

这里的问题是控制器只是第一次被调用,登录后没有任何反应。

3 个答案:

答案 0 :(得分:4)

正如我之前所说,您将需要使用一个存储用户信息的服务。将用户信息附加到此服务,无论您在何处对用户进行身份验证。如果您对验证的最佳方法有疑问,那将是一个单独的问题,但您可能希望使用登录工厂进行实际身份验证(以及任何授权)。然后,您可以将登录服务注入该工厂。我创建了Plunker here作为参考。

var app = angular.module('myApp', []);

app.service('SessionService', function () {
  this.attachUser = function(userId, fName){
    this.userId = userId;
    this.fName = fName;
  }
});


app.controller('MainCtrl', function($scope, SessionService){
  // You will want to invoke attachUser some other way (perhaps on authentication), this is for test purposes only
  SessionService.attachUser(1234, 'John');

  $scope.userName = SessionService.fName; 
});

上面的代码是您服务的一个示例。这将充当会话处理程序并存储有关用户的重要信息。然后,控制器MainCtrl可以使用依赖注入来调用SessionService中的属性。我在本文开头提到的部分SessionService.attachUser(userId, fName)很可能会住在登录工厂。

这是最佳选择的原因是因为它解耦了您的应用程序。它将会话(实际上存储在全局变量中)放在指定存储该数据的位置。它使它可维护。例如,您不需要查找$ rootScope的每次出现。

编辑: New plunker使用rootScope broadcast / on来捕获更改

答案 1 :(得分:1)

事件是沟通的首选方式,需要采取其他措施。发生了一个其他事情可能对行动感兴趣的行动。如你所说,它还减少了范围污染。

在这种情况下关于使用服务的评论只是部分准确。所有登录逻辑都可以并且应该放入特定于日志记录和注销的单个服务中。然后,当登录发生时,该服务将广播该事件。

module.service('LoginHelper', function($rootScope) {
    this.loginUser = function(username, password) {
        // on success
        $rootScope.broadcast('loggedIn', logginUserData)
    }
    this.logout = function() {
        // on success
        $rootScope.broadcast('loggedOut')
    }
})

登录的数据应该由服务存储和访问。

或者,可以在$ rootScope上使用$ emit。然后,您才能够注意“登录”&#39;在$ rootScope上的事件将略微减少开销。

答案 2 :(得分:0)

避免观看

事件是满足此类要求的适当方式,例如alex指出的方式。一个示例的插件:http://plnkr.co/edit/v6OXjOXZzF9McMvtn6hG?p=preview

但对于这种特殊情况,我并不认为&#34;棱角分明的方式&#34; &#34;方式&#34; 。鉴于$broadcast和/或$emit如何工作的性质(即事件以角度方式工作的默认方式),我会避免它们......(阅读docs以了解原因)。简而言之,这些机制旨在触发侦听器(附加到某些范围)上/下范围层。你真的不需要这一切。 ($emit)的参考code

我通常依赖其他事件传播机制(考虑到这种要求模式)。

app.controller('MainCtrl', function($scope, SessionService, $document){
  // You will want to invoke attachUser some other way (perhaps on authentication), this is for test purposes only
  $scope.isLoggedIn = false;
  $document.bind('$loggedin', function(){
      $scope.isLoggedIn = true;
      $scope.user = SessionService.fName;
  });
  $scope.logout = function() {
    SessionService.attachUser(null, null);
    $scope.isLoggedIn = false;
  };
});

app.controller('LoginCtrl', function($scope, SessionService, $document){
  $scope.doLogin = function() {
    console.log('doLogin');
    SessionService.attachUser(1234, $scope.username);  
    var doc = $document[0];
    var evt = new Event('$loggedin');
    doc.dispatchEvent(evt);
  };
});

Plunk

当然,当您完成该视图时,请始终进行清理。处理该控制器范围内的$destroy事件并取消绑定事件处理程序...

$scope.$on('$destroy', function() {
    $document.unbind('$loggedin');
};

有关如何使用DOM触发事件的更多信息,请参阅MDN

更新:[9月24日]

这是一个小指令设置,它说明了这一点:

app.directive('ngNest', function($parse, $compile, $timeout){
  var end = false;
  var level = 0;

  var fnPostLink = function(scope, element, attrs) {    
    //console.log('start:', ++fnPostLink.count);
    var lvl = attrs.level;
    if(!lvl) {
      throw 'Level not specified';
    }
    var create = document.createElement.bind(document);

    var level = parseInt(lvl);
    var count = 0;
    var div = create('div');
    div.setAttribute('ng-controller', 'DummyCtrl');
    var cls = function() {
      return 'margin ' + (count % 2 ? 'even' : 'odd');
      //return 'margin even';
    };
    div.setAttribute('class', cls());
    var node = div;
    while(count++ < level - 1) {
      var child = create('div');
      child.setAttribute('ng-controller', 'DummyCtrl');
      child.setAttribute('class', cls());
      node.appendChild(child);
      node = child;      
    }

    node.setAttribute('ng-controller', 'FinalCtrl');
    node.innerHTML = 'foo';
    var $new = $compile(div)(scope);
    var el = element;
    el.append($new);
  };

  fnPostLink.count = 0;
  var fnPreLink = function(scope, element, attrs) {    
    //console.log('prelink');    
  };

  var api = {    
    link: {
      post: fnPostLink,
      pre: fnPreLink
    },
    template: '<div></div>',
    scope: {},
    restrict: 'E',    
    replace: true
  };

  return api;

});

它简单地将divs连接到控制器上。我附上这两个控制器:

app.controller('DummyCtrl', function($scope){  
});

app.controller('FinalCtrl', function($scope, $document){
  $scope.$on('$myEvt', function(){
    console.log('$myEvt', $scope.$id, new Date().getTime());
  });

  $document.bind('$myEvt', function(){
    console.log('$myEvt', $scope.$id, new Date().getTime());
  });
});

FinalCtrl被添加到尾部; DummyCtrl被添加到其余部分。

在html模板中,我执行以下操作:

<ng-nest level="10"></ng-nest>

在html文件中还有一个嵌套标记,可以手动放在那里......

可在此处找到整个代码:https://gist.github.com/deostroll/a9a2de04d3913f021f13

以下是我从浏览器中获得的结果:

Live reload enabled.
$broadcast 1443074421928
$myEvt 14 1443074421929
$myEvt 19 1443074421930
DOM 1443074426405
$myEvt 14 1443074426405
$myEvt 19 1443074426405

当我完成$ broadcast时,您会注意到刻度线的差异。我在$ rootScope上做了一个$ broadcast;因此,角度沿着范围树 depth-first 向下走,并触发附加相应范围的那些侦听器,并且按顺序 ... $ emit&amp; $ broadcast源代码也验证了这一事实。