[javascript] Can you pass parameters to an AngularJS controller on creation?

I have a controller responsible for communicating with an API to update properties of a user, name, email, etc. Each user has an 'id' which is passed from the server when the profile page is viewed.

I would like to pass this value to the AngularJS controller so it knows what the API entry point is for the current user. I've tried passing the value in ng-controller. For example:

function UserCtrl(id, $scope, $filter) {

$scope.connection = $resource('api.com/user/' + id)

and in the HTML

<body ng-controller="UserCtrl({% id %})">

where {% id %} print the id sent from the server. but I get errors.

What is the correct way to pass a value into a controller on its creation?

This question is related to javascript angularjs

The answer is


Notes:

This answer is old. This is just a proof of concept on how the desired outcome can be achieved. However, it may not be the best solution as per some comments below. I don't have any documentation to support or reject the following approach. Please refer to some of the comments below for further discussion on this topic.

Original Answer:

I answered this to Yes you absolutely can do so using ng-init and a simple init function.

Here is the example of it on 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)
  };


});

If using angular-ui-router, then this is the correct solution: https://github.com/angular-ui/ui-router/wiki#resolve

Basically, you declare a set of dependecies to "resolve" before the controller is instantiated. You may declare dependencies for each of your "states". These dependencies are then passed in the controller's "constructor".


This question is old but I struggled for a long time trying to get an answer to this problem that would work for my needs and did not easily find it. I believe my following solution is much better than the currently accepted one, perhaps because angular has added functionality since this question was originally posed.

Short answer, using the Module.value method allows you to pass data into a controller constructor.

See my plunker here

I create a model object, then associate it with the module's controller, referencing it with the name '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>

The constructor in my controller then accepts a parameter with that same identifier 'model' which it can then access.

Controller

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

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

Notes:

I'm using manual initialization of bootstrapping, which allows me to initialize my model before sending it over to angular. This plays much more nicely with existing code, as you can wait to set up your relevant data and only compile the angular subset of your app on demand when you want to.

In the plunker I've added a button to alert the values of the model object that was initially defined in javascript and passed to angular, just to confirm that angular is truly referencing the model object, rather than copying it and working with a copy.

On this line:

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

I'm passing the MyController object into the Module.controller function, rather than declaring as a function inline. I think this allows us to far more clearly define our controller object, but Angular documentation tends to do it inline so I thought it bears clarification.

I'm using the "controller as" syntax and assigning values to the "this" property of MyController, rather than using the "$scope" variable. I believe this would work fine using $scope just as well, the controller assignment would then look something like this:

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

and the controller constructor would have a signature like this:

function MyController ($scope, model) {

If for whatever reason you wanted to, you could also attach this model as a value of a second module, which you then attach as a dependency to your primary module.

I believe his solution is much better than the currently accepted one because

  1. The model passed to the controller is actually a javascript object, not a string that gets evaluated. It is a true reference to the object and changes to it affect other references to this model object.
  2. Angular says that the accepted answer's use of ng-init is a misuse, which this solution doesn't do.

The way Angular seems to work in most all other examples I've seen has the controller defining the data of the model, which never made sense to me, there is no separation between the model and the controller, that doesn't really seem like MVC to me. This solution allows you to really have a completely separate model object which you pass into the controller. Also of note, if you use the ng-include directive you can put all your angular html in a separate file, fully separating your model view and controller into separate modular pieces.


I found passing variables from $routeProvider usefull.

For example, you use one controller MyController for multiple screens, passing some very important variable "mySuperConstant" inside.

Use that simple structure:

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

    }

This also works.

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>

It looks like the best solution for you is actually a directive. This allows you to still have your controller, but define custom properties for it.

Use this if you need access to variables in the wrapping scope:

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>

Use this if you don't need access to variables in the wrapping scope:

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>

I'm very late to this and I have no idea if this is a good idea, but you can include the $attrs injectable in the controller function allowing the controller to be initialized using "arguments" provided on an element, e.g.

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>

Again, no idea if this is a good idea, but it seems to work and is another alternative.


If ng-init is not for passing objects into $scope, you can always write your own directive. So here is what I got:

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>

But my approach can only modify objects, which are already defined at controller.


You can do it when setting up the routes for e.g.

 .when('/newitem/:itemType', {
            templateUrl: 'scripts/components/items/newEditItem.html',
            controller: 'NewEditItemController as vm',
            resolve: {
              isEditMode: function () {
                return true;
              }
            },
        })

And later use it as

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

So here when we set up the route we sent :itemType and retrive it later from $routeParams.


There is another way to pass parameters to a controller by injecting $routeParams into your controller and then using url parameters described here What's the most concise way to read query parameters in AngularJS?


I didn't really like any of the solutions here for my particular use case, so I figured I'd post what I did because I didn't see it here.

I simply wanted to use a controller more like a directive, within a ng-repeat loop:

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

Now, in order to access the objParameter on creation within each DirectiveLikeController (or to get the up-to-date objParameter at ANY time), all I need to do is inject $scope and call $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); 
});

The only real downside that I see is that it requires the parent controller to know that the parameter is named objParameter.


Here is a solution (based on Marcin Wyszynski's suggestion) which works where you want to pass a value into your controller but you aren't explicitly declaring the controller in your html (which ng-init seems to require) - if, for example, you are rendering your templates with ng-view and declaring each controller for the corresponding route via 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>

In this solution, CurrentUser is a service which can be injected into any controller, with the .name property then available.

Two notes:

  • a problem I've encountered is that .name gets set after the controller loads, so as a workaround I have a short timeout before rendering username on the controller's scope. Is there a neat way of waiting until .name has been set on the service?

  • this feels like a very easy way to get a current user into your Angular App with all the authentication kept outside Angular. You could have a before_filter to prevent non-logged in users getting to the html where your Angular app is bootstrapped in, and within that html you could just interpolate the logged in user's name and even their ID if you wanted to interact with the user's details via http requests from your Angular app. You could allow non-logged in users to use the Angular App with a default 'guest user'. Any advice on why this approach would be bad would be welcome - it feels too easy to be sensible!)


One way of doing that would be having a separate service that can be used as a 'vessel' for those arguments where they're public data members.


Like @akonsu and Nigel Findlater suggest, you can read the url where url is index.html#/user/:id with $routeParams.id and use it inside the controller.

your app:

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

app.config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/:type/:id', {templateUrl: 'myView.html', controller: 'myCtrl'});
}]);

the resource service

app.factory('MyElements', ['$resource', function($resource) {
     return $resource('url/to/json/:type/:id', { type:'@type', id:'@id' });
}]);

the controller

app.controller('MyCtrl', ['$scope', '$routeParams', 'MyElements', function($scope, $routeParams, MyElements) {
    MyElements.get({'type': $routeParams.type, "id": $routeParams.id }, function(elm) {
        $scope.elm = elm;
    })
}]);

then, elm is accessible in the view depending on the id.


The view should not dictate config

In Angular, the template should never dictate configuration, which is inherently what people desire when they want to pass arguments to controllers from a template file. This becomes a slippery slope. If config settings are hard-coded in templates (such as by a directive or controller argument attribute), you can no longer re-use that template for anything but that single use. Soon you'll want to re-use that template, but with different config and now in order to do so you'll either be pre-processing the templates to inject variables before it gets passed to angular or using massive directives to spit out giant blocks of HTML so you re-use all of the controller HTML except for the wrapper div and it's arguments. For small projects it's no big deal. For something big (what angular excels at), it gets ugly quick.

The Alternative: Modules

This type of configuration is what modules were designed to handle. In many angular tutorials people have a single module for their entire application, but really the system is designed and fully supports many small modules each which wrap small pieces of the total application. Ideally, controllers, modules etc would be declared in separate files and stitched together in specific re-usable chunks. When your application is designed this way, you get a lot of re-use in addition to easy controller arguments.

The example below has 2 modules, re-using the same controller, but each with their own config settings. That config settings are passed in via dependency injection using module.value. This adheres to the angular way because we have the following: constructor dependency injection, reusable controller code, reusable controller templates (the controller div could easily be included with ng-include), easily unit-testable system without HTML, and lastly re-usable modules as the vehicle for stitching the pieces together.

Here's an example:

<!-- 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>

See it in action: jsFiddle.


No, It is not possible. I think you can use ng-init as hack http://docs.angularjs.org/api/ng.directive:ngInit.


This is an expansion of @Michael Tiller's excellent answer. His answer works for initializing variables from the view by injecting the $attrs object into the controller. The problem occurs if the same controller is called from $routeProvider when you navigate by routing. Then you get injector error Unknown provider : $attrsProvider because $attrs is only available for injection when the view is compiled. The solution is to pass the variable (foo) through $routeParams when initializing controller from route and by $attrs when initializing controller from view. Here's my solution.

From Route

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

This handles a url such as '/mypage/bar'. As you can see foo is passed by url param and we provide the $injector with a blank object for $attrs so no injector errors.

From View

<div ng-controller="MyPageController" data-foo="bar">

</div>

Now the controller

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'

});