Angular 控制器作用域继承与服务
Angular controller scope inheritance vs service
在我的网站上,我有一个导航栏组件,我想为我最终加载的每个 ng-view 自定义它。目前我正在这样做如下。我有一个导航栏本身的 NavCtrl,我的 ng-view 指令位于这个控制器的范围之外。我使用导航栏服务来实现导航栏中的 change/override 功能,例如,我的每个视图都需要覆盖导航栏保存按钮的点击处理程序。 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>
我在想我是不是把这个弄得太复杂了。我认为同样的事情可以通过在 NavCtrl 的范围内嵌套 ng-view 指令来实现。这样我就可以在每个视图控制器中覆盖 $scope.save。
但是大多数文档都指出服务是跨控制器共享资源的首选方式。一种方法比另一种更好吗?为什么?
请指教。谢谢
前言和一些历史
让我提出一些既不利用服务也不利用范围继承的建议。这是我试图解决的类似问题。我有通过 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 的另一个选项) 或并行状态。
However I will have to repeat the <button ng-click="save()">Save</button>
element in every template if since all my views need that button.
这条评论澄清了一些事情,在这种情况下,我不一定推荐 方法。相反,这个特定的 UX 可以被认为更像是一个 static UX,我会做一些不同的事情 still 避免任何 服务或作用域继承。我建议使用以下内容:
应用级别
您的应用级导航仍然可以借鉴其他答案的建议,但是让我们忽略 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>
]
每个视图级别控制器
这里有一些假设:
- 只有这些类型的视图中的一个一直存在(意味着每个应用程序状态一个 view/controller,否则您可能有 2 个控制器试图处理保存请求)
- 您的视图作为子视图驻留在您的应用程序模板中
您的视图遵循普通的视图控制器范例(这意味着它们不是具有隔离作用域的特殊指令)
. 控制器 'someController', [
'$范围'
($作用域)->
$scope.$on 'saveRqst`, (data)-> #do something for this view
]
现在我知道这看起来有点像范围继承位,但事实并非如此。无论如何,你必须为每个视图控制器逻辑定义你的保存逻辑,如果我理解你在问题和其他答案评论中提出的内容,就无法解决这个问题。这种 事件总线 方法的一个好处是它很容易跟踪逻辑。如果您在代码中添加注释,那么另一个有使用 Java 或 Flex 或其他任何经验的开发人员将能够轻松地加快实现附加视图的速度。保存逻辑。
所以我已经介绍了两种方法。一种是 ,而这种是更静态但每个视图唯一的方法。
在我的网站上,我有一个导航栏组件,我想为我最终加载的每个 ng-view 自定义它。目前我正在这样做如下。我有一个导航栏本身的 NavCtrl,我的 ng-view 指令位于这个控制器的范围之外。我使用导航栏服务来实现导航栏中的 change/override 功能,例如,我的每个视图都需要覆盖导航栏保存按钮的点击处理程序。 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>
我在想我是不是把这个弄得太复杂了。我认为同样的事情可以通过在 NavCtrl 的范围内嵌套 ng-view 指令来实现。这样我就可以在每个视图控制器中覆盖 $scope.save。
但是大多数文档都指出服务是跨控制器共享资源的首选方式。一种方法比另一种更好吗?为什么?
请指教。谢谢
前言和一些历史
让我提出一些既不利用服务也不利用范围继承的建议。这是我试图解决的类似问题。我有通过 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 的另一个选项) 或并行状态。
However I will have to repeat the
<button ng-click="save()">Save</button>
element in every template if since all my views need that button.
这条评论澄清了一些事情,在这种情况下,我不一定推荐
应用级别
您的应用级导航仍然可以借鉴其他答案的建议,但是让我们忽略 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>
]
每个视图级别控制器
这里有一些假设:
- 只有这些类型的视图中的一个一直存在(意味着每个应用程序状态一个 view/controller,否则您可能有 2 个控制器试图处理保存请求)
- 您的视图作为子视图驻留在您的应用程序模板中
您的视图遵循普通的视图控制器范例(这意味着它们不是具有隔离作用域的特殊指令)
. 控制器 'someController', [ '$范围' ($作用域)-> $scope.$on 'saveRqst`, (data)-> #do something for this view ]
现在我知道这看起来有点像范围继承位,但事实并非如此。无论如何,你必须为每个视图控制器逻辑定义你的保存逻辑,如果我理解你在问题和其他答案评论中提出的内容,就无法解决这个问题。这种 事件总线 方法的一个好处是它很容易跟踪逻辑。如果您在代码中添加注释,那么另一个有使用 Java 或 Flex 或其他任何经验的开发人员将能够轻松地加快实现附加视图的速度。保存逻辑。
所以我已经介绍了两种方法。一种是