使用服务跟踪用户状态并使 $watch() 正常工作

Using a service to track user state and getting $watch() to work?

本文关键字:常工作 工作 watch 服务 跟踪 用户 状态      更新时间:2023-09-26

我是AngularJS的新手,正在努力开发一个依赖于登录用户的NAV的网站。我决定使用状态模型设计,将 Nav 控制器作为每个页面的父状态。 这一切都有效。

app.config(function ($stateProvider, $httpProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('login');
$stateProvider
    .state('admin', {
        templateUrl: 'partials/Nav.html',
        controller: 'NavigationController'
    })
    .state('users', {
        url:'/users',
        templateUrl:'partials/Users.html',
        controller: 'UserController',
        parent: 'admin'
    })
    .state('devices', {
        url:'/devices',
        templateUrl:'partials/Devices.html',
        controller: 'DeviceController',
        parent: 'admin'
    }) ...

但是,我需要跟踪用户的状态,我认为我应该使用服务来执行此操作。所以我有一个服务(UserStatus(,基本上跟踪用户的状态(经过身份验证和用户名(。 其他控制器将这些更改传达给此服务。到目前为止,所有工作都有效。

(function(){
    var app = angular.module('butler-user-status', []);
    app.service( 'UserStatusService', ['$http', function($http) {
        //
        // As a service, the 'this' when the code is called will likely point to other things.
        // To avoid this issue, I save 'this' in a local variable and then the functions can 
        // refer to that variable and it will always be the right one!
        //
        console.log('User Status: Initializing service...');
        var userStatusService = this;
        userStatusService.state = {currentUsername: '', 
                                    authenticated: false};

        $http.get('/currentUser', {})
            .success(function(data, status, headers, config){
                // console.log('currentUser responded with data: '+JSON.stringify(data.username));
                if (data.username != null) {
                    console.log('UserStat: on success name='+data.username);
                    console.log('UserStat: on success authenticated='+true);
                };
            }).error(function(){
                // console.log('currentUser responded with error');
                console.log('UserStat: on error');
                userStatusService.state.currentUsername = '';
                userStatusService.state.authenticated = false;
            });
        this.login =  function(username) {
            console.log('Login: Setting username  and authenticated: username='+username);
            userStatusService.state.currentUsername = username;
            userStatusService.state.authenticated = true;
            console.log('Login: user logged in');
        };
        this.logout = function() { 
            userStatusService.state.authenticated = false; 
            console.log('Login: user logged out');
        };
    }]);
})();

然后,我想让 Nav 控制器跟踪用户状态的变化,因此我在服务中的变量上设置了一个$scope.$watch(),并在服务更改时更新控制器中的变量。 然后,HTML 页面可以通过引用控制器的变量来更改其表示形式。

(function() {
    var app = angular.module('butler-navigation', ['butler-user-status']);
    app.controller('NavigationController', ['$scope', 'UserStatusService', function($scope, UserStatusService) {

        var navigationController = this;
        var isAuthenticated = UserStatusService.state.authenticated;
        var currentUsername = UserStatusService.state.currentUsername;
        console.log('Butler-Nav: at init: username ='+UserStatusService.state.currentUsername);
        console.log('Butler-Nav: at init: auth = '+ UserStatusService.state.authenticated);

        $scope.$watch('UserStatusService.state', function (newValue, oldValue) {
            console.log('Butler-Nav: Updating isAuthenticated to:'+newValue+' from '+oldValue);
            navigationController.isAuthenticated = newValue;
        });

    }]);
})();

出于某种原因,我收到了对$watch侦听器的初始回调,但对于 newValue 和 oldValue 都没有定义。 但是我将服务中的值设置为实际值,而不是空值等。

我做错了什么? 提前感谢您帮助新手。

问题是$watch计算在作用域上定义的表达式。您的 UserStatusService 不是,因此它返回未定义。您有两种选择。

选项 1:定义一个函数,该函数获取导航控制器范围内的状态:

$scope.getUserState = function () {
    return UserStatusService.state;
};

您想在此处使用 getter 函数,以便它始终具有更新的值。然后看这个表达式:

$scope.$watch("getUserState()", function (newValue, oldValue) {
    // Do stuff on change here
});

这是一个不错的选择,因为无论如何,你可能希望在范围内提供此信息,以便 UI 可以相应地做出响应。

选项 2:您始终可以只观察函数的结果:

$scope.$watch(function () {
    return UserStatusService.state;
}, function (newValue, oldValue) {
    // Do stuff on change here
});

在每个摘要周期中,将评估第一个函数,并将返回的结果与前一个值进行比较。这显然更简单,并且避免在示波器上放置任何东西。


根据要求更新更多说明

你似乎对普通的JavaScript变量作用域和Angular所谓的"作用域"感到困惑。

Angular 使用单词"scope"来指代可用于视图中表达式的特殊对象。当你在视图中编写类似"{{foo}}"的东西时,Angular 会用附加到范围对象的名为 foo 的变量的值填充它。范围对象由许多指令创建,并将相互嵌套。此嵌套行为类似于变量作用域,这是名称的来源。此页面包含更多详细信息。

控制器可以通过 Angular 注入的 $scope 变量访问范围对象。附加到 $scope 变量的任何内容都将可供该控制器的视图访问。如果未将某些内容显式附加到$scope对象,则无法访问该对象。

现在,这就是$watch函数的用武之地。当您在视图中放置表达式(例如"{{foo}}"(时,Angular 会在表达式上放置一个"监视"(在本例中为"foo"(。当用户与应用程序交互时,它会触发摘要循环。(还有其他方法。Angular 将遍历所有被监视的表达式,评估它们,看看它们是否与上次不同。对于您的问题至关重要的是,这些表达式仅引用作用域上的变量。因此,在这个魔法起作用之前,您必须在示波器上附加一些东西。这是我的第一个选择所做的。

现在我的第二个选择更聪明一些。Angular允许您观察函数的结果。整个摘要循环过程是相同的,只是它不是计算作用域上的表达式,而是计算函数。

我做了一个小提琴来展示这一切。

不是实际的答案,但我对你如何解决这个问题的看法。

有什么理由不只在控制器的作用域上公开 UserStatusService 并使用指令将逻辑移动到部分吗?

我个人更喜欢将服务公开到范围内并利用指令,其中 angular 具有最大的力量。