您可以在创建时将参数传递给 AngularJS 控制器吗?
Posted
技术标签:
【中文标题】您可以在创建时将参数传递给 AngularJS 控制器吗?【英文标题】:Can you pass parameters to an AngularJS controller on creation? 【发布时间】:2013-01-09 12:39:31 【问题描述】:我有一个控制器负责与 API 通信以更新用户、姓名、电子邮件等的属性。每个用户都有一个'id'
,当查看个人资料页面时,它会从服务器传递过来。
我想将此值传递给 AngularJS 控制器,以便它知道当前用户的 API 入口点是什么。我试过在ng-controller
中传递值。例如:
function UserCtrl(id, $scope, $filter)
$scope.connection = $resource('api.com/user/' + id)
在 html 中
<body ng-controller="UserCtrl(% id %)">
% id %
打印从服务器发送的 id。但我得到错误。
在创建控制器时将值传递给控制器的正确方法是什么?
【问题讨论】:
如果您将 id 作为 url 的一部分,那么您可以读取 url 我遇到了非常相似的问题,我解决了这个问题,正如我在我的答案中发布的那样。有时使用库我们会忽略 javascript 函数调用的简单基本概念。 @nickponline 21 岁以上你仍然认为这是不可能的吗? Pass variables to AngularJS controller, best practice?的可能重复 【参考方案1】:注意事项:
这个答案是旧的。这只是关于如何实现预期结果的概念证明。但是,根据下面的一些 cmets,它可能不是最佳解决方案。我没有任何文件支持或拒绝以下方法。有关此主题的进一步讨论,请参阅下面的一些 cmets。
原答案:
我回答了这个问题
是的,您绝对可以使用 ng-init
和一个简单的 init 函数来做到这一点。
这是plunker上的例子
HTML
<!DOCTYPE html>
<html ng-app="angularjs-starter">
<head lang="en">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl" ng-init="init('James Bond','007')">
<h1>I am name id</h1>
</body>
</html>
JavaScript
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope)
$scope.init = function(name, id)
//This function is sort of private constructor for controller
$scope.id = id;
$scope.name = name;
//Based on passed argument you can make a call to resource
//and initialize more objects
//$resource.getMeBond(007)
;
);
【讨论】:
谢谢你——让我省了很多麻烦,而且它非常适合我的情况。我必须用对象 ID 初始化我的控制器,这恰到好处。 From docs:The only appropriate use of ngInit for aliasing special properties of ngRepeat, as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
答案清楚地表明文档建议反对这种方法,但有人可以指出文档提供官方解决方案的位置吗?
好吧,我认为文档只是在不给出理由的情况下推荐反对这种方法。我认为这是一个绝妙的方法。如果框架作者不希望框架以对他们来说是“错误”的方式使用,那么他们不应该以这种“错误”的方式使用框架......并提供有关“正确”的方式!
一个警告——如果你试图绑定一个范围值,或者真的做任何期望值出现在控制器函数中的事情,它已经在@之前运行了控制器函数987654328@ 发生——见plnkr.co/edit/donCm6FRBSX9oENXh9WJ?p=preview【参考方案2】:
我已经很晚了,我不知道这是否是一个好主意,但是您可以在控制器函数中包含 $attrs
可注入,从而允许使用元素上提供的“参数”来初始化控制器, 例如
app.controller('modelController', function($scope, $attrs)
if (!$attrs.model) throw new Error("No model for modelController");
// Initialize $scope using the value of the model attribute, e.g.,
$scope.url = "http://example.com/fetch?model="+$attrs.model;
)
<div ng-controller="modelController" model="foobar">
<a href="url">Click here</a>
</div>
同样,不知道这是否是个好主意,但它似乎可行并且是另一种选择。
【讨论】:
如何使用这种方法传递对象? var obj = a: 1, b: 2; 这是一个非常合理的解决混合应用问题的解决方案。 @Neil:你必须对你的 json 对象进行字符串化,然后在控制器中解析它。不是更好的解决方案,但它可能会起作用。 Michael 的解决方案适用于类似字符串的参数... 这实际上比使用ng-init
更可靠——如果你试图绑定到一个范围值,它已经在ng-init
发生之前运行了控制器函数——见plnkr.co/edit/donCm6FRBSX9oENXh9WJ?p=preview
我没有用指令等重新发明***。如果你有一个控制器,你想用几个不同的初始化参数在多个视图中使用,这是最简单的 i> 和最可靠的解决方案。【参考方案3】:
这也有效。
Javascript:
var app = angular.module('angularApp', []);
app.controller('MainCtrl', function($scope, name, id)
$scope.id = id;
$scope.name = name;
// and more init
);
HTML:
<!DOCTYPE html>
<html ng-app="angularApp">
<head lang="en">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
<script src="app.js"></script>
<script>
app.value("name", "James").value("id", "007");
</script>
</head>
<body ng-controller="MainCtrl">
<h1>I am name id</h1>
</body>
</html>
【讨论】:
这是一个很好的解决方案,可以与现有的构造函数注入机制一起使用。本质上,您是在创建两个简单的服务,分别称为“name”和“id”。在构造过程中,注入器负责按名称匹配它们。参见:docs.angularjs.org/guide/providers的价值配方部分 不错。请注意,这也适用于对象,而不仅仅是字符串等原始类型。 我更喜欢这个解决方案。我能够通过参数传递模块化我的重复代码。干杯! 这看起来是最地道的方式,但我不确定 Angular 团队是否推荐它 这是否实质上设置了全局键/值对?这些是否可以限制在给定的控制器实例上,以便仅在主控制器下的子控制器上拾取它?如果不是,这似乎与在窗口级别设置常规全局 Javascript 变量并没有太大区别。【参考方案4】:视图不应支配配置
在 Angular 中,模板永远不应该指定配置,这是人们想要从模板文件向控制器传递参数时所固有的期望。这变成了一个滑坡。如果配置设置在模板中是硬编码的(例如通过指令或控制器参数属性),则您不能再将该模板重复用于除单一用途之外的任何用途。很快你就会想要重新使用那个模板,但是使用不同的配置,现在为了做到这一点,你要么在模板被传递给 angular 之前预处理模板以注入变量,要么使用大量指令来吐出巨大的HTML 块,因此您可以重复使用除包装器 div 及其参数之外的所有控制器 HTML。对于小型项目,这没什么大不了的。对于大的东西(Angular 擅长的东西),它很快就会变得丑陋。
替代方案:模块
这种类型的配置是模块设计用来处理的。在许多 Angular 教程中,人们为他们的整个应用程序使用了一个模块,但实际上系统是经过设计的并且完全支持许多小模块,每个小模块都包含整个应用程序的一小部分。理想情况下,控制器、模块等将在单独的文件中声明,并在特定的可重用块中拼接在一起。当您的应用程序以这种方式设计时,除了简单的控制器参数之外,您还可以获得很多重用。
下面的示例有 2 个模块,重复使用相同的控制器,但每个模块都有自己的配置设置。该配置设置是使用module.value
通过依赖注入传递的。这遵循了 angular 方式,因为我们有以下内容:构造函数依赖注入、可重用的控制器代码、可重用的控制器模板(控制器 div 可以很容易地包含在 ng-include 中)、无需 HTML 的易于单元测试的系统,最后是可重用的模块作为将各个部分拼接在一起的工具。
这是一个例子:
<!-- index.html -->
<div id="module1">
<div ng-controller="MyCtrl">
<div>foo</div>
</div>
</div>
<div id="module2">
<div ng-controller="MyCtrl">
<div>foo</div>
</div>
</div>
<script>
// part of this template, or a JS file designed to be used with this template
angular.element(document).ready(function()
angular.bootstrap(document.getElementById("module1"), ["module1"]);
angular.bootstrap(document.getElementById("module2"), ["module2"]);
);
</script>
<!-- scripts which will likely in be in their seperate files -->
<script>
// MyCtrl.js
var MyCtrl = function($scope, foo)
$scope.foo = foo;
MyCtrl.$inject = ["$scope", "foo"];
// Module1.js
var module1 = angular.module('module1', []);
module1.value("foo", "fooValue1");
module1.controller("MyCtrl", MyCtrl);
// Module2.js file
var module2 = angular.module('module2', []);
module2.value("foo", "fooValue2");
module2.controller("MyCtrl", MyCtrl);
</script>
查看实际操作:jsFiddle。
【讨论】:
我已经投了赞成票,但我想对您的贡献表示感谢。这个答案让我走上了一条更好的道路。当人们分享最佳实践而不仅仅是 hacky sn-ps 时,*** 是最好的。这太棒了:“模板永远不应该指定配置,当人们想从模板文件向控制器传递参数时,这本来就是人们想要的”。感谢您将激光视觉带入问题的核心。【参考方案5】:就像@akonsu 和 Nigel Findlater 建议的那样,您可以使用 $routeParams.id
读取 url 为 index.html#/user/:id
的 url,并在控制器中使用它。
您的应用:
var app = angular.module('myApp', [ 'ngResource' ]);
app.config(['$routeProvider', function($routeProvider)
$routeProvider.when('/:type/:id', templateUrl: 'myView.html', controller: 'myCtrl');
]);
资源服务
app.factory('MyElements', ['$resource', function($resource)
return $resource('url/to/json/:type/:id', type:'@type', id:'@id' );
]);
控制器
app.controller('MyCtrl', ['$scope', '$routeParams', 'MyElements', function($scope, $routeParams, MyElements)
MyElements.get('type': $routeParams.type, "id": $routeParams.id , function(elm)
$scope.elm = elm;
)
]);
然后,elm
可以在视图中访问,具体取决于id
。
【讨论】:
【参考方案6】:如果ng-init
不是用于将对象传递给$scope
,您始终可以编写自己的指令。所以这就是我得到的:
http://jsfiddle.net/goliney/89bLj/
Javasript:
var app = angular.module('myApp', []);
app.directive('initData', function($parse)
return function(scope, element, attrs)
//modify scope
var model = $parse(attrs.initData);
model(scope);
;
);
function Ctrl1($scope)
//should be defined
$scope.inputdata = foo:"east", bar:"west";
HTML:
<div ng-controller="Ctrl1">
<div init-data="inputdata.foo=123; inputdata.bar=321"></div>
</div>
但我的方法只能修改已经在控制器中定义的对象。
【讨论】:
作为参考,这很好用,但在当前版本的 Angular 上,它是 $attrs (vs attrs)【参考方案7】:看起来对您来说最好的解决方案实际上是一个指令。这允许您仍然拥有您的控制器,但为它定义自定义属性。
如果您需要访问包装范围内的变量,请使用它:
angular.module('myModule').directive('user', function ($filter)
return
link: function (scope, element, attrs)
$scope.connection = $resource('api.com/user/' + attrs.userId);
;
);
<user user-id="% id %"></user>
如果您不需要访问包装范围内的变量,请使用此选项:
angular.module('myModule').directive('user', function ($filter)
return
scope:
userId: '@'
,
link: function (scope, element, attrs)
$scope.connection = $resource('api.com/user/' + scope.userId);
;
);
<user user-id="% id %"></user>
【讨论】:
【参考方案8】:我发现从 $routeProvider 传递变量很有用。
例如,您将一个控制器 MyController 用于多个屏幕,在其中传递一些非常重要的变量“mySuperConstant”。
使用那个简单的结构:
Router:
$routeProvider
.when('/this-page',
templateUrl: 'common.html',
controller: MyController,
mySuperConstant: "123"
)
.when('/that-page',
templateUrl: 'common.html',
controller: MyController,
mySuperConstant: "456"
)
.when('/another-page',
templateUrl: 'common.html',
controller: MyController,
mySuperConstant: "789"
)
MyController:
MyController: function ($scope, $route)
var mySuperConstant: $route.current.mySuperConstant;
alert(mySuperConstant);
【讨论】:
【参考方案9】:您可以在设置例如路线时这样做
.when('/newitem/:itemType',
templateUrl: 'scripts/components/items/newEditItem.html',
controller: 'NewEditItemController as vm',
resolve:
isEditMode: function ()
return true;
,
)
以后用它作为
(function ()
'use strict';
angular
.module('myApp')
.controller('NewEditItemController', NewEditItemController);
NewEditItemController.$inject = ['$http','isEditMode',$routeParams,];
function NewEditItemController($http, isEditMode, $routeParams)
/* jshint validthis:true */
var vm = this;
vm.isEditMode = isEditMode;
vm.itemType = $routeParams.itemType;
)();
所以当我们设置路由时,我们发送 :itemType 并稍后从 $routeParams 中检索它。
【讨论】:
【参考方案10】:还有另一种将参数传递给控制器的方法,方法是将 $routeParams 注入控制器,然后使用此处描述的 url 参数What's the most concise way to read query parameters in AngularJS?
【讨论】:
【参考方案11】:这个问题很老,但我挣扎了很长时间,试图找到一个适合我需要的问题的答案,但并没有轻易找到它。我相信我的以下解决方案比目前接受的解决方案要好得多,也许是因为 angular 自从这个问题最初提出以来就增加了功能。
简短的回答,使用 Module.value 方法允许您将数据传递到控制器构造函数中。
见我的笨蛋here
我创建了一个模型对象,然后将它与模块的控制器关联,并使用名称“模型”引用它
HTML / JS
<html>
<head>
<script>
var model = "id": 1, "name":"foo";
$(document).ready(function()
var module = angular.module('myApp', []);
module.value('model', model);
module.controller('MyController', ['model', MyController]);
angular.bootstrap(document, ['myApp']);
);
function confirmModelEdited()
alert("model name: " + model.name + "\nmodel id: " + model.id);
</script>
</head>
<body >
<div ng-controller="MyController as controller">
id: controller.model.id <br>
name: <input ng-model="controller.model.name"/>controller.model.name
<br><button ng-click="controller.incrementId()">increment ID</button>
<br><button onclick="confirmModelEdited()">confirm model was edited</button>
</div>
</body>
</html>
然后,我的控制器中的构造函数接受具有相同标识符“模型”的参数,然后它可以访问该参数。
控制器
function MyController (model)
this.model = model;
MyController.prototype.incrementId = function()
this.model.id = this.model.id + 1;
注意事项:
我正在使用bootstrapping 的手动初始化,它允许我在将模型发送到 Angular 之前对其进行初始化。这与现有代码配合得更好,因为您可以等待设置相关数据并仅在需要时按需编译应用程序的 Angular 子集。
在 plunker 中,我添加了一个按钮来提醒最初在 javascript 中定义并传递给 angular 的模型对象的值,只是为了确认 angular 真正引用了模型对象,而不是复制它并使用复印件。
在这一行:
module.controller('MyController', ['model', MyController]);
我将 MyController 对象传递给 Module.controller 函数,而不是声明为内联函数。我认为这使我们能够更清楚地定义我们的控制器对象,但 Angular 文档倾向于内联,所以我认为它需要澄清。
我使用“controller as”语法并将值分配给 MyController 的“this”属性,而不是使用“$scope”变量。我相信使用 $scope 也可以正常工作,控制器分配看起来像这样:
module.controller('MyController', ['$scope', 'model', MyController]);
并且控制器构造函数会有这样的签名:
function MyController ($scope, model)
如果出于任何原因,您还可以将此模型附加为第二个模块的值,然后将其作为依赖项附加到主模块。
我相信他的解决方案比目前接受的解决方案要好得多,因为
-
传递给控制器的模型实际上是一个 javascript 对象,而不是一个被评估的字符串。它是对对象的真实引用,对它的更改会影响对该模型对象的其他引用。
Angular 表示接受的答案对 ng-init 的使用是一种误用,而此解决方案没有这样做。
在我见过的大多数其他示例中,Angular 的工作方式似乎都是由控制器定义模型的数据,这对我来说从来没有意义,模型和控制器之间没有分离,这没有对我来说真的很像 MVC。该解决方案允许您真正拥有一个完全独立的模型对象,并将其传递给控制器。另外值得注意的是,如果您使用 ng-include 指令,您可以将所有 Angular html 放在一个单独的文件中,将您的模型视图和控制器完全分离为单独的模块化部分。
【讨论】:
【参考方案12】:如果使用 angular-ui-router,那么这是正确的解决方案:https://github.com/angular-ui/ui-router/wiki#resolve
基本上,您在实例化控制器之前声明一组要“解决”的依赖项。您可以为每个“状态”声明依赖项。然后将这些依赖项传递到控制器的“构造函数”中。
【讨论】:
【参考方案13】:这样做的一种方法是拥有一个单独的服务,该服务可以用作那些作为公共数据成员的参数的“容器”。
【讨论】:
最佳答案,IMO。我目前也在某些情况下为那些敢于按 F5 的用户使用浏览器存储,因此保持这种状态。【参考方案14】:这是@Michael Tiller 的excellent answer 的扩展。他的答案适用于通过将$attrs
对象注入控制器来从视图初始化变量。如果在通过路由导航时从$routeProvider
调用同一控制器,则会出现问题。然后你会得到注入器错误Unknown provider : $attrsProvider
,因为$attrs
仅在编译视图时可用于注入。解决方案是在从路由初始化控制器时通过$routeParams
传递变量(foo),在从视图初始化控制器时通过$attrs
传递变量(foo)。这是我的解决方案。
从路线
$routeProvider.
when('/mypage/:foo',
templateUrl: 'templates/mypage.html',
controller: 'MyPageController',
caseInsensitiveMatch: true,
resolve:
$attrs: function ()
return ;
);
这会处理诸如'/mypage/bar'
之类的网址。如您所见,foo 是通过 url 参数传递的,我们为 $injector
提供了一个用于 $attrs
的空白对象,因此不会出现注入器错误。
从视图
<div ng-controller="MyPageController" data-foo="bar">
</div>
现在是控制器
var app = angular.module('myapp', []);
app.controller('MyPageController',['$scope', '$attrs', '$routeParams'], function($scope, $attrs, $routeParams)
//now you can initialize foo. If $attrs contains foo, it's been initialized from view
//else find it from $routeParams
var foo = $attrs.foo? $attrs.foo : $routeParams.foo;
console.log(foo); //prints 'bar'
);
【讨论】:
【参考方案15】:对于我的特定用例,我真的不喜欢这里的任何解决方案,所以我想我会发布我所做的,因为我在这里没有看到它。
我只是想在 ng-repeat 循环中使用更像指令的控制器:
<div ng-repeat="objParameter in [id:'a',id:'b',id:'c']">
<div ng-controller="DirectiveLikeController as ctrl"></div>
</div>
现在,为了访问每个 DirectiveLikeController 中创建时的objParameter
(或随时获取最新的 objParameter),我需要做的就是注入 $scope 并调用 $scope.$eval('objParameter')
:
var app = angular.module('myapp', []);
app.controller('DirectiveLikeController',['$scope'], function($scope)
//print 'a' for the 1st instance, 'b' for the 2nd instance, and 'c' for the 3rd.
console.log($scope.$eval('objParameter').id);
);
我看到的唯一真正的缺点是它需要父控制器知道参数名为objParameter
。
【讨论】:
【参考方案16】:不,这是不可能的。我认为您可以将 ng-init 用作 hack http://docs.angularjs.org/api/ng.directive:ngInit。
【讨论】:
我不敢苟同,但您可以使用 ng-init 和使用 init 函数来完成此操作。 一个警告——如果你试图绑定一个范围值,或者真的做任何期望值出现在控制器函数中的事情,它已经在@之前运行了控制器函数987654323@ 发生——见plnkr.co/edit/donCm6FRBSX9oENXh9WJ?p=preview 是的,这是可能的。至少,您会使用数据参数或其他任何东西。但绝对有可能。【参考方案17】:这是一个解决方案(基于 Marcin Wyszynski 的建议),它适用于您想要将值传递到控制器但您没有在 html 中明确声明控制器(ng-init 似乎需要) - 如果,例如,您正在使用 ng-view 渲染模板,并通过 routeProvider 为相应的路由声明每个控制器。
JS
messageboard.directive('currentuser', ['CurrentUser', function(CurrentUser)
return function(scope, element, attrs)
CurrentUser.name = attrs.name;
;
]);
html
<div ng-app="app">
<div class="view-container">
<div ng-view currentuser name="testusername" class="view-frame animate-view"></div>
</div>
</div>
在这个解决方案中,CurrentUser 是一个服务,它可以被注入到任何控制器中,然后可以使用 .name 属性。
两个音符:
我遇到的一个问题是 .name 在控制器加载后被设置,因此作为一种解决方法,我在控制器范围内呈现用户名之前有一个短暂的超时。有没有一种巧妙的方法可以等到在服务上设置了 .name?
这感觉就像是让当前用户进入您的 Angular 应用程序的一种非常简单的方法,而所有身份验证都保留在 Angular 之外。您可以有一个 before_filter 来防止未登录的用户访问您的 Angular 应用程序引导所在的 html,如果您想与用户的详细信息进行交互,您可以在该 html 中插入登录用户的名称甚至他们的 ID通过来自 Angular 应用程序的 http 请求。您可以允许未登录的用户通过默认的“访客用户”使用 Angular 应用程序。欢迎任何关于为什么这种方法不好的建议 - 感觉太容易理解了!)
【讨论】:
以上是关于您可以在创建时将参数传递给 AngularJS 控制器吗?的主要内容,如果未能解决你的问题,请参考以下文章
Json 对象作为参数传递给 dynamicallay 使用 javascript/angularjs 创建的元素点击事件