【问题标题】:Angular controller scope inheritance vs serviceAngular 控制器范围继承与服务
【发布时间】:2016-03-29 02:06:14
【问题描述】:

在我的网站上,我有一个导航栏组件,我想为最终加载的每个 ng-view 进行自定义。目前我这样做如下。我有一个导航栏本身的 NavCtrl,我的 ng-view 指令位于此控制器的范围之外。我使用导航栏服务来更改/覆盖导航栏中的功能,例如,我的每个视图都需要覆盖导航栏的保存按钮的单击处理程序。 NavbarService 具有设置保存功能的钩子。在 NavCtrl 中 $scope.save = NavbarService.save

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

app.config(function($routeProvider) {
  $routeProvider.when('/world', {templateUrl : 'world.html', controller : 'WorldCtrl'}).otherwise({templateUrl : 'hello.html', controller : 'HelloCtrl'});
});

app.service('NavbarService', function() {
  var message = 'Save button not clicked yet',
      saveFunction = function() {
    this.setMessage('Default save called');
  };
  
  this.save = function() {
    _saveFunction();
  };
  
  this.setSaveFunction = function(funct) {
    _saveFunction = funct;
  };
  
  this.setMessage = function(newMessage) {
    message = newMessage;
  };
  
  this.getMessage = function() {
    return message;
  }
});

app.controller('NavCtrl', function($scope, $location, NavbarService) {
  $scope.message = NavbarService.getMessage();
  $scope.save = NavbarService.save;
  $scope.world = function() {
    $location.path('/world');
  };
  
  $scope.hello = function() {
    $location.path('/hello');
  };
  
  $scope.$watch(NavbarService.getMessage, function(newValue) {
    $scope.message = newValue;
  });
});

app.controller('HelloCtrl', function($scope, NavbarService) {
  $scope.init = function() {
    NavbarService.setSaveFunction(function() {
      NavbarService.setMessage('Save method called from the HelloCtrl');
    });
  };
});

app.controller('WorldCtrl', function($scope, NavbarService) {
  $scope.init = function() {
    NavbarService.setSaveFunction(function() {
      NavbarService.setMessage('Save method called from the WorldCtrl');
    });
  };
});
<html lang="en">

<head>
  <title>My App</title>
</head>

<body ng-app="myApp">
  <nav ng-controller="NavCtrl">
    <button ng-click="save()">Save</button>
    <button ng-click="world()">Go to world</button>
    <button ng-click="hello()">Go to hello</button>
    <pre>{{message}}</pre>
  </nav>

  <div ng-view onload="init()"></div>

  <script type="text/ng-template" id="hello.html">
    <h2>Active view is Hello</h2>
  </script>

  <script type="text/ng-template" id="world.html">
    <h2>Active view is World</h2>
  </script>

  <script src="https://code.jquery.com/jquery-2.1.4.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-route.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-resource.js"></script>
</body>

</html>

我想知道我是否让这太复杂了。我认为只需将 ng-view 指令嵌套在 NavCtrl 的范围内即可实现相同的目的。这样我就可以在每个视图控制器中覆盖 $scope.save。

但大多数文档都指出,服务是跨控制器共享资源的首选方式。一种方式比另一种更好吗?为什么?

请指教。谢谢。

【问题讨论】:

  • 这里有几个反模式。你永远不应该从控制器/服务中修改 DOM。它们仅用于管理数据/状态。 #message 应该是模板的一部分,并通过模板更改进行更改,或者有自己的控制器来监听路由并修改驱动消息的范围属性。
  • 感谢您的评论。我只需要演示“保存”按钮在活动视图控制器上执行保存方法。 #message 只是为了显示日志。换句话说,又快又脏。我已经修改了我的代码 sn-p 以消除您指出的违规行为。
  • 我认为这个问题应该转移到 codereview 网站,因为它提供了完整的工作代码并要求审查。

标签: angularjs angularjs-scope angular-services


【解决方案1】:

但是,如果我的所有视图都需要该按钮,我将不得不在每个模板中重复 &lt;button ng-click="save()"&gt;Save&lt;/button&gt; 元素。

这条评论澄清了一些事情,在这种情况下,我不一定推荐dynamic/declarative 方法。相反,这个特定的用户体验可以被认为是一个静态用户体验,我会做一些不同的事情,仍然避免任何服务或作用域继承。我建议使用以下内容:

应用级别

您的应用级导航仍然可以借用其他答案的建议,但是让我们忽略动态/声明性内容。所以导航看起来像:

<div class="navbar navbar-submenu2">
    <ul class="nav navbar-nav navbar-submenu2">

        <!-- container for dynamic view-specific menu content -->
        <li nx-view-menu-container class="nav navbar-nav">
        </li>

        <!-- static nav items -->
        <li class="nav navbar-nav">
            <a ng-click="saveRqst()">
                <span class="current-del-name">Save</span>
            </a>
        </li>

应用级控制器会有这样的:

.controller 'appController', [
    '$scope'
    ($scope)->
         $scope.saveRqst = ->
             $scope.$root.$broadcast 'saveRqst', <addt. data if applicable>
]

每个视图级别的控制器

这做了一些假设:

  • 只有其中一种视图存在(意味着每个应用程序状态只有一个视图/控制器,否则您可能有 2 个控制器尝试处理保存请求)
  • 您的视图作为子视图存在于您的应用程序模板中
  • 您的视图遵循普通视图控制器范例(这意味着它们不是具有隔离范围的特殊指令)

    .controller 'someController', [ '$范围' ($范围)-> $scope.$on 'saveRqst`, (data)-> #do something for this view ]

现在我知道这看起来有点像范围继承位,但事实并非如此。无论如何,你必须为每个视图控制器逻辑定义你的保存逻辑,如果我理解你在问题和其他答案的评论中提出的内容,就没有办法解决这个问题。这种事件总线方法的一个优点是它很容易跟踪逻辑。如果您注释了您的代码,那么其他有使用 Java 或 Flex 或其他任何东西的经验的开发人员将能够轻松地加快实现其他视图的速度。保存逻辑。

所以我提出了 2 种方法来解决这个问题。一种是more dynamic, declarative approach,这种方法更静态,但每个视图都是唯一的。

【讨论】:

    【解决方案2】:

    前言和一些历史

    让我提出一些既不利用服务也不利用范围继承的东西。这是我试图解决的类似问题。我有通过 ui.router's ui-view 加载的视图(我们基本上是在谈论应用程序状态,对吧?)。我有我正常的应用程序导航和子导航,像这样......

    ['findings', 'tasks', 'reports', 'blah']
    

    ...所有视图共有的。但我也有每个视图的一些特定 UI,我希望将它们连接到该视图的 $scope/controller。换句话说,我可能有一个特定于当前视图的下拉菜单,以及另一个视图的预输入。我选择使用指令以声明方式解决这个问题,而不是试图强制一些继承覆盖范式。

    指令

    nxViewMenuContainer

    首先我做了一个指令,它只是一个容器,用于容纳特定于视图导航元素的声明元素。

    .directive 'nxViewMenuContainer', [
        ()->
            dir =
                restrict: 'EAC'
                link: ($scope, elem, attrs)->
                    if !elem.attr 'id'
                        elem.attr 'id', 'nx-view-menu-container'
    
                    $scope.$root.$on '$stateChangeSuccess', ->
                        elem.empty()
    ]
    

    这是这样实现的:

    <div class="navbar navbar-submenu2">
            <ul class="nav navbar-nav navbar-submenu2">
    
                <!-- container for dynamic view-specific menu content -->
                <li nx-view-menu-container class="nav navbar-nav">
                </li>
    
                <!-- static nav items -->
                <li class="nav navbar-nav" ng-repeat="nav in app.subnav">
                    <a><span class="current-del-name">{{nav.label}}</span></a>
                </li>
    

    nxViewMenu

    该指令在每个视图上声明,并用作移动到上述指令的特定于视图的元素容器。

    .directive 'nxViewMenu', [
        ()->
            dir =
                restrict: 'EAC'
                link: ($scope, elem, attrs)->
    
                    elem.ready ->
                        name = attrs.nxViewMenu or '#nx-view-menu-container'
                        target = $(name)
    
                        if !target.length
                            return
    
                        target.empty()
                        target.append elem
    
    ]
    

    在每个视图中,我可能希望动态菜单出现在其他地方(在这种情况下,在应用级别的导航容器中)我在视图模板中声明它。

    查看 1

    <div class="nx-view-menu">
        <a class="btn btn-default">
            <i class="fa fa-lg fa-bars nx-clickout-filter"></i>
            <!--<label name="panel-title" style="color:#58585b; padding-left:5px;" class="nx-clickout-filter">Quick Links</label>-->
        </a>
    </div>                
    

    查看 2

    <div class="nx-view-menu">
        <input typeahead="...">
    </div>
    

    查看 3

    <div class="nx-view-menu">
        <date-picker>...</date-picker>
    </div>
    

    结束论点

    • 首先,您是以声明的方式做事,因此一旦您理解了指令,就很容易遵循逻辑。
    • 它使顶级控制器不了解特定视图的逻辑,同时仍以一致的方式显示 DOM
    • 它绕过了对特殊服务或一些 hacky 消息总线的需求
    • 它简化了您的整体 DOM 结构,因为您不需要设置一些双视图容器(在我的情况下,这是使用 ui.router 的另一个选项)或并行状态。

    【讨论】:

    • 我会尽量设置演示
    • 嘿@jusopi 感谢您的建议。您的解决方案将适用于我的情况。但是,如果我的所有视图都需要该按钮,我将不得不在每个模板中重复 &lt;button ng-click="save()"&gt;Save&lt;/button&gt; 元素。这只是所有视图使用的元素之一。我总是可以将该 html 封装在另一个模板中,并将其包含在我的所有视图中。但我不知道我是否喜欢从根本上删除和重新添加 dom 元素的想法。我想如果两个视图在navbar 上具有相同的元素,那么在 locationChange 期间我们会看到轻微的闪烁。
    • 今晚将设置一个 plnkr 并验证它确实是一个问题。我喜欢你的方法在失败方面更具描述性的想法。我会确切地知道我没有实现$scope.save 方法的地方。
    • 对于始终存在的元素,只是逻辑不同(意味着当前视图控制器实现该功能)然后让您的顶级应用程序使用$scope.$root.$broadcast('save'),每个视图的控制器使用$scope.$on('save', ...)。这假设您只有一个当前视图(听起来像您)。还要确保通过$scope.$on('$destroy, cleanupFunc) 清理您的听众。
    • 关于你关于添加/删除DOM元素时闪烁的评论,我还没有经历过。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多