您可以在创建时将参数传递给AngularJS控制器吗?


278

我有一个控制器,负责与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。但我得到了错误。

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


6
如果您将ID作为网址的一部分,则可以阅读该网址
akonsu 2013年

我遇到了非常类似的问题,并且按照我的回答进行了解决。有时使用库,我们会忽略JavaScript函数调用的简单基本概念。
Jigar Patel

@nickponline 21岁以上之后,您仍然认为不可能吗?
om471987 2013年

Answers:


362

笔记:

这个答案很旧。这仅仅是关于如何实现所需结果的概念证明。但是,根据下面的一些评论,它可能不是最佳解决方案。我没有任何文档可以支持或拒绝以下方法。请参考下面的一些评论,以进一步讨论该主题。

原始答案:

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

这是在punker上的例子

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


});

5
谢谢您-为我省去了很多麻烦,它非常适合我的情况。我必须使用对象ID初始化控制器,这是正确的。
Masonoise

26
来自文档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.
Sergey Goliney

8
答案清楚地表明该文档的建议是反对这种方法的,但是有人可以指出我该文档提供官方解决方案的地方吗?
Michael Pell 2014年

53
好吧,我认为文档在不提出原因的情况下仅推荐这种方法是很糟糕的。我认为这是一个绝妙的方法。如果框架作者不希望以“错误”的方式使用框架,那么他们就不应以“错误”的方式使用框架...并提供有关“正确的方法!
Shawn de Wet

2
一句警告-如果您试图绑定到作用域值,或者确实做了任何期望该值出现在控制器功能中的事情,则它已经在ng-init发生之前运行了控制器功能-请参阅plnkr.co/edit / donCm6FRBSX9oENXh9WJ?p =预览
drzaus

143

我已经很晚了,我不知道这是否是一个好主意,但是您可以$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>

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


3
如何使用这种方法传递对象?var obj = {a:1,b:2};
尼尔

3
这是解决混合应用程序问题的非常合理的解决方案。
superluminary 2014年

2
@Neil:您必须先对json对象进行字符串化处理,然后在控制器内部对其进行解析。不是更好的解决方案,但它可能会起作用。迈克尔(Michael)的解决方案适用于类似字符串的参数...
M'sieur Toph'2014年

1
这实际上比使用更可靠ng-init-如果您尝试绑定到作用域值,则它已经在ng-init发生之前运行了控制器功能-请参阅plnkr.co/edit/donCm6FRBSX9oENXh9WJ?p=preview
drzaus,2014年

2
我并没有通过指令等来彻底改造轮子。如果您有一个要在带有多个不同初始化参数的多个视图中使用的控制器,那么这是最简单,最可靠的解决方案。
埃里克·H.

39

这也有效。

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>

3
这是与现有构造函数注入机制一起使用的很好的解决方案。从本质上讲,您正在创建两个名为“ name”和“ id”的简单服务。喷油器负责在施工期间按名称进行匹配。请参阅:docs.angularjs.org/guide/providers
Todd

1
真好 注意,这也适用于对象,而不仅仅是像字符串这样的基本类型。
jbustamovej 2014年

我更喜欢这种解决方案。我可以通过参数传递将重复的代码模块化。干杯!
agentpx 2015年

这似乎是最惯用的方式,但我不确定棱角团队是否推荐使用这种方式
VinGarcia

这实际上是否设置了全局键/值对?可以将这些作用域限定于给定的控制器实例,以便仅在主控制器下的子控制器上使用它吗?如果不是这样的话,这与在窗口级别设置常规的全局Javascript变量似乎并没有什么不同。
jpierson

16

该视图不应指示配置

在Angular中,模板绝不应该规定配置,这是人们想要从模板文件将参数传递给控制器​​时所固有的需求。这将成为滑坡。如果配置设置是硬编码在模板中的(例如通过指令或控制器参数属性),则您只能再使用该模板,而不能再使用该模板。很快,您将想要重新使用该模板,但现在使用不同的配置,因此您可以对模板进行预处理,以在将变量传递给angular之前注入变量,或使用大量指令吐出巨无霸。 HTML块,因此您可以重复使用所有控制器HTML,但包装div及其参数除外。对于小型项目,没什么大不了的。对于大的东西(角度擅长的地方),它很快就会变得丑陋。

替代方案:模块

这类配置是设计模块处理的。在许多角度教程中,人们为整个应用程序只有一个模块,但实际上,系统是经过设计的,并且完全支持许多小模块,每个小模块都包装了整个应用程序的小块。理想情况下,控制器,模块等应在单独的文件中声明,并在特定的可重用块中缝合在一起。以这种方式设计应用程序时,除了简单的控制器参数外,您还将获得大量重用。

下面的示例有2个模块,重复使用相同的控制器,但是每个模块都有自己的配置设置。通过使用依赖项注入来传递配置设置module.value。这遵循角度的方法,因为我们具有以下内容:构造函数依赖项注入,可重用的控制器代码,可重用的控制器模板(controller 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


我已经投票支持,但是我要感谢您的贡献。这个答案使我走上了一条更好的道路。当人们分享最佳实践而不仅仅是骇人听闻的片段时,stackoverflow是最好的。这很了不起:“模板绝不应该规定配置,这是人们想要从模板文件将参数传递给控制器​​时固有的需求”。感谢您将激光视觉技术带入问题的核心。
食虫性

15

像@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


8

如果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)
Dinis Cruz

8

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

如果需要访问包装范围中的变量,请使用此方法:

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>

7

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

    }

6

您可以在设置路线时执行此操作,例如

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



3

这个问题很老,但是我努力了很长时间才能找到一个可以满足我的需求并且不容易找到它的答案。我相信我的后续解决方案要比当前接受的解决方案好得多,这也许是因为自从这个问题最初提出以来,角度就增加了功能。

简短的答案,使用Module.value方法允许您将数据传递到控制器构造函数中。

在这里看到我的朋克

我创建一个模型对象,然后将其与模块的控制器相关联,并使用“模型”名称进行引用

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

笔记:

我正在使用引导程序的手动初始化,这使我可以在将模型发送到angular之前对其进行初始化。这可以与现有代码配合使用,因为您可以等待设置相关数据,并且仅在需要时才按需编译应用的角度子集。

在插件中,我添加了一个按钮来警告最初在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) {

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

我相信他的解决方案比目前接受的解决方案好得多,因为

  1. 传递给控制器​​的模型实际上是一个javascript对象,而不是要评估的字符串。它是对对象的真实引用,对其所做的更改会影响对此模型对象的其他引用。
  2. Angular表示,接受的答案使用ng-init是一种误用,此解决方案无法解决。

在我所见过的大多数其他示例中,Angular的工作方式似乎都是由控制器定义模型数据,这对我来说从来没有道理,模型与控制器之间没有分离,这看起来似乎并不像我的MVC。该解决方案使您可以真正拥有一个完全独立的模型对象,并将其传递给控制器​​。还要注意的是,如果使用ng-include指令,则可以将所有angular html放在单独的文件中,从而将模型视图和控制器完全分成单独的模块。


2

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

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


1

做到这一点的一种方法是拥有一个单独的服务,可以将它们用作公共数据成员的论点的“容器”。


最佳答案,IMO。目前,在某些情况下,我也为那些敢于按下F5的用户使用浏览器存储,因此该状态得以维持。
睡鼠

1

我真的不喜欢这里针对我的特定用例的任何解决方案,所以我认为我会发布我所做的事情,因为我在这里没有看到它。

我只是想在ng-repeat循环中使用更像指令的控制器:

<div ng-repeat="objParameter in [{id:'a'},{id:'b'},{id:'c'}]">
  <div ng-controller="DirectiveLikeController as ctrl"></div>
</div>

现在,为了访问objParameter每个DirectiveLikeController中的on创建(或随时获取最新的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


0

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


4
我希望有所不同,但是您可以使用ng-init和init函数来完成此操作。
Jigar Patel

一句警告-如果您试图绑定到作用域值,或者确实做了任何期望该值出现在控制器功能中的事情,则它已经在ng-init发生之前运行了控制器功能-请参阅plnkr.co/edit / donCm6FRBSX9oENXh9WJ?p =预览
drzaus

是的,有可能。至少,您将使用数据参数或其他任何参数。但是它绝对可能。
胡安

0

这是一个解决方案(基于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 App。您可以使用一个before_filter来防止未登录的用户进入Angular应用程序引导所在的html,并且在该html中,如果您想与用户的详细信息进行交互,就可以插值已登录用户的名称甚至其ID。通过来自Angular应用的http请求。您可以允许未登录的用户与默认的“访客用户”一起使用Angular App。任何关于为什么这种方法不好的建议都将受到欢迎-感觉太容易了!


0

这是@Michael Tiller 出色回答的扩展。他的回答是通过将$attrs对象注入到控制器中来初始化视图中的变量。如果$routeProvider通过路由导航时调用了同一控制器,则会出现问题。然后会出现注入器错误,Unknown provider : $attrsProvider因为$attrs仅在编译视图时才可进行注入。解决方案是在$routeParams从路由$attrs初始化控制器时以及从视图初始化控制器时传递变量(foo)。这是我的解决方案。

从路线

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

这会处理一个网址,例如'/mypage/bar'。如您所见,foo是通过url参数传递的,并且我们为foo提供了$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'

});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.