你能在创建时将参数传递给AngularJS控制器吗?

我有一个控制器负责与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。但是会有错误。

在创建控制器时将值传递给控制器的正确方法是什么?

349225 次浏览

不,不可能。我认为你可以使用ng-init作为hack http://docs.angularjs.org/api/ng.directive:ngInit

注:

这个答案已经过时了。这只是一个关于如何实现预期结果的概念证明。然而,根据下面的一些评论,这可能不是最好的解决方案。我没有任何文档来支持或拒绝以下方法。请参考下面的一些评论,以进一步讨论这个主题。

最初的回答:

我回答了这个 是的,你绝对可以使用ng-init和一个简单的init函数来做到这一点

下面是它在砰砰作响上的例子

超文本标记语言

<!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)
};




});

还有另一种方法将参数传递给控制器,即向控制器中注入$routeParams,然后使用这里描述的url参数在AngularJS中读取查询参数最简洁的方法是什么?

如果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>

但是我的方法只能修改已经在控制器中定义的对象。

就像@akonsu和Nigel Findlater建议的那样,你可以读取url,其中url是index.html#/user/:id$routeParams.id,并在控制器内部使用它。

你的应用程序:

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

这也是可行的。

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>

我对此很晚,我不知道这是否是一个好主意,但你可以在控制器函数中包括$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>

同样,不知道这是否是一个好主意,但它似乎有效,是另一种选择。

一种方法是有一个单独的服务,可以用作这些参数的“容器”,其中它们是公共数据成员。

看起来对你来说最好的解决方案实际上是一个指令。这允许你仍然拥有你的控制器,但是为它定义自定义属性。

如果你需要访问包装范围内的变量,使用这个:

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>

我发现从$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);


}

视图不应该指定配置

在Angular中,模板永远不应该指定配置,这正是人们想要从模板文件中将参数传递给控制器时所期望的。这就形成了滑坡效应。如果配置设置是硬编码在模板中(例如通过指令或控制器参数属性),你不能再重复使用该模板,除了这一次使用。很快你就会想要重用这个模板,但是使用不同的配置,现在为了做到这一点,你要么在模板传递给angular之前对其进行预处理以注入变量,要么使用大量的指令吐出大块的HTML,这样你就可以重用所有的控制器HTML,除了包装器div和它的参数。对于小项目来说,这没什么大不了的。对于一些大的东西(angular所擅长的),它很快就会变得丑陋。

替代方案:模块

这种类型的配置是模块设计用来处理的。在许多angular教程中,人们的整个应用程序都只有一个模块,但实际上系统的设计和完全支持许多小模块,每个小模块都包含整个应用程序的一小部分。理想情况下,控制器、模块等应该在单独的文件中声明,并在特定的可重用块中拼接在一起。当您的应用程序以这种方式设计时,除了简单的控制器参数之外,您还可以得到大量重用。

下面的例子有两个模块,重复使用相同的控制器,但每个模块都有自己的配置设置。配置设置是使用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

你可以在设置路由时这样做。

 .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中检索它。

如果使用angular-ui-router,那么这是正确的解决方案:https://github.com/angular-ui/ui-router/wiki#resolve

基本上,在控制器实例化之前声明一组要“解析”的依赖项。你可以为你的每个“状态”声明依赖关系。然后将这些依赖项传递到控制器的“构造函数”中。

这里有一个解决方案(基于Marcin Wyszynski的建议),当你想要传递一个值到你的控制器,但你没有显式地在你的html中声明控制器(ng-init似乎需要)-例如,如果你用ng-view渲染你的模板,并通过routeProvider为相应的路由声明每个控制器。

JS

messageboard.directive('currentuser', ['CurrentUser', function(CurrentUser) {
return function(scope, element, attrs) {
CurrentUser.name = attrs.name;
};
}]);

超文本标记语言

<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,如果你想通过http请求与Angular应用中的用户进行交互,你可以在html中插入登录用户的姓名甚至ID。你可以允许非登录用户使用默认的“guest user”来使用Angular应用。任何关于为什么这种方法不好的建议都是受欢迎的-感觉太容易理智了!)

这个问题很老了,但我挣扎了很长时间,试图找到一个适合我需要的答案,但并不容易找到。我相信我下面的解决方案比目前接受的解决方案要好得多,也许是因为angular在最初提出这个问题时增加了功能。

简单的回答,使用模块。Value方法允许您将数据传递给控制器构造函数。

看我的活塞在这里

我创建了一个模型对象,然后将它与模块的控制器相关联,引用它的名称为'model'

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>

然后,我的控制器中的构造函数接受一个具有相同标识符“model”的参数,然后它可以访问这个标识符。

控制器

function MyController (model) {
this.model = model;
}


MyController.prototype.incrementId = function() {
this.model.id = this.model.id + 1;
}

注:

我正在使用引导的手动初始化,这允许我在将模型发送到angular之前初始化它。这对于现有的代码来说效果更好,因为你可以等待相关数据的设置,只在你想要的时候按需编译应用程序的angular子集。

在plunker中,我添加了一个按钮来提醒最初在javascript中定义并传递给angular的模型对象的值,只是为了确认angular真正引用了模型对象,而不是复制它并使用副本。

在这一行:

module.controller('MyController', ['model', MyController]);

我将MyController对象传递到模块中。控制器函数,而不是声明为内联函数。我认为这可以让我们更清楚地定义控制器对象,但Angular文档倾向于内联,所以我认为有必要澄清一下。

我使用“controller as”语法并将值分配给MyController的“this”属性,而不是使用“$scope”变量。我相信使用$scope也能很好地工作,控制器赋值看起来是这样的:

module.controller('MyController', ['$scope', 'model', MyController]);

控制器构造函数会有这样的签名:

function MyController ($scope, model) {

如果出于任何原因,您还可以将此模型作为第二个模块的值附加,然后将其作为依赖项附加到您的主模块。

我相信他的解决办法比目前公认的好得多,因为

  1. 传递给控制器的模型实际上是一个javascript对象,而不是要计算的字符串。它是对对象的真实引用,对它的更改会影响对该模型对象的其他引用。
  2. Angular说,接受的答案使用ng-init是一种误用,而这个解决方案不会这样做。

在我所见过的大多数其他例子中,Angular的工作方式似乎都是由控制器定义模型的数据,这对我来说从来没有意义,模型和控制器之间没有分离,这对我来说不太像MVC。这个解决方案允许你真正拥有一个完全独立的模型对象,你传递给控制器。同样值得注意的是,如果你使用ng-include指令,你可以把所有的angular html放在一个单独的文件中,把你的模型视图和控制器完全分离成独立的模块块。

这是@Michael Tiller的优秀的答案的扩展。他的答案适用于通过将$attrs对象注入控制器来从视图初始化变量。当通过路由导航时,如果从$routeProvider调用相同的控制器,则会发生问题。然后你会得到注入器错误Unknown provider : $attrsProvider,因为$attrs只在视图编译时可用于注入。解决方案是在从route初始化控制器时通过$routeParams传递变量(foo),在从view初始化控制器时通过$attrs传递变量。这是我的解决方案。

从路线

$routeProvider.
when('/mypage/:foo', {
templateUrl: 'templates/mypage.html',
controller: 'MyPageController',
caseInsensitiveMatch: true,
resolve: {
$attrs: function () {
return {};
}
}
});

它处理诸如'/mypage/bar'这样的url。正如你所看到的,foo是通过url参数传递的,我们为$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'


});

对于我的特定用例,我真的不喜欢这里的任何解决方案,所以我想我应该发布我所做的,因为我在这里没有看到它。

我只是想在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