¿Se pueden pasar parámetros a un controlador AngularJS en la creación?

278

Tengo un controlador responsable de comunicarme con una API para actualizar las propiedades de un usuario, nombre, correo electrónico, etc. Cada usuario tiene uno 'id'que se pasa del servidor cuando se visualiza la página de perfil.

Me gustaría pasar este valor al controlador AngularJS para que sepa cuál es el punto de entrada de API para el usuario actual. He intentado pasar el valor ng-controller. Por ejemplo:

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

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

y en el HTML

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

donde {% id %}imprime el id enviado desde el servidor. pero me salen errores

¿Cuál es la forma correcta de pasar un valor a un controlador en su creación?

nickponline
fuente
66
si tuviera la identificación como parte de la URL, entonces podría leer la URL
akonsu
Tuve un problema muy similar y lo resolví como lo publiqué en mi respuesta. A veces, al usar las bibliotecas, pasamos por alto un concepto fundamental simple de llamada a la función de JavaScript.
Jigar Patel
@nickponline Después de más de 21 años, ¿todavía crees que no es posible?
om471987
Posible duplicado de variables
T.Todua

Respuestas:

362

Notas:

Esta respuesta es vieja. Esto es solo una prueba de concepto sobre cómo se puede lograr el resultado deseado. Sin embargo, puede que no sea la mejor solución según algunos comentarios a continuación. No tengo ninguna documentación para respaldar o rechazar el siguiente enfoque. Consulte algunos de los comentarios a continuación para obtener más información sobre este tema.

Respuesta original

Respondí esto a Sí, absolutamente puedes hacerlo usando ng-inituna simple función de inicio.

Aquí está el ejemplo de ello en 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)
  };


});
Jigar Patel
fuente
55
Gracias por esto, me ahorré muchos rasguños y funcionó perfectamente para mi situación. Tuve que inicializar mi controlador con una ID de objeto, y esto fue justo.
Masonoise
26
De documentos :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
La respuesta deja en claro que el documento recomienda contra este enfoque, pero ¿alguien puede indicarme dónde proporcionan la solución oficial los documentos?
Michael Pell
53
Bueno, creo que los documentos son deficientes al recomendar este enfoque sin dar razones. Creo que este es un enfoque brillante. Si los autores del marco no quieren que el marco se use de una manera "incorrecta", entonces no deben permitir que se use de esa manera "incorrecta" ... y proporcionar orientación sobre el " ¡manera correcta!
Shawn de Wet
2
una palabra de advertencia: si está intentando vincularse a un valor de ámbito, o realmente hace algo que espera que el valor esté presente en la función del controlador, ya está ejecutando la función del controlador antes de que ng-initocurra, consulte plnkr.co/edit / donCm6FRBSX9oENXh9WJ? p = vista previa
drzaus
143

Llegué muy tarde a esto y no tengo idea si es una buena idea, pero puede incluir el $attrsinyectable en la función del controlador, lo que permite que el controlador se inicialice utilizando los "argumentos" proporcionados en un elemento, por ejemplo

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>

Nuevamente, no tengo idea si es una buena idea, pero parece funcionar y es otra alternativa.

Michael Tiller
fuente
3
¿Cómo pasaría un objeto usando este enfoque? var obj = {a: 1, b: 2};
Neil
3
Esta es una solución muy razonable para el problema de producir una aplicación híbrida.
superluminary
2
@Neil: debe stringificar su objeto json y luego analizarlo dentro del controlador. No es la mejor solución, pero puede funcionar. La solución de Michael está bien para parámetros similares a cadenas ...
M'sieur Toph '
1
esto es en realidad más confiable que usarlo ng-init: si está intentando vincular a un valor de alcance, ya está ejecutando la función del controlador antes de que ng-initocurra; consulte plnkr.co/edit/donCm6FRBSX9oENXh9WJ?p=preview
drzaus el
2
No entiendo reinventar la rueda con directivas, etc. Si tiene un controlador que desea usar en varias vistas con algunos parámetros de inicialización diferentes, esta es la solución más fácil y confiable.
Eric H.
39

Esto tambien funciona.

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>
Parque Jonghee
fuente
3
Esta es una buena solución que funciona con el mecanismo de inyección del constructor existente. En esencia, estás creando dos servicios simples llamados 'nombre' e 'id'. El inyector se encarga de unirlos por nombre durante la construcción. Ver: la sección Receta de valor
Todd
1
Agradable. Tenga en cuenta que esto también funciona con objetos, no solo con tipos primitivos como cadenas.
jbustamovej
Me gusta más esta solución. Pude modularizar mis códigos duplicados a través del paso de parámetros. ¡Salud!
agentpx
Esto parece la forma más idiomática, pero no estoy seguro de que lo recomiende el equipo angular
VinGarcia
¿Esto configura esencialmente pares clave / valor globales? ¿Se pueden aplicar estos ámbitos a una instancia de controlador determinada para que solo se recoja en controladores secundarios bajo el principal? Si no, parece que esto no es realmente muy diferente a simplemente establecer una variable Javascript global regular en el nivel de la ventana.
jpierson
16

La vista no debe dictar la configuración

En Angular, la plantilla nunca debe dictar la configuración, que es inherentemente lo que las personas desean cuando quieren pasar argumentos a los controladores desde un archivo de plantilla. Esto se convierte en una pendiente resbaladiza. Si los ajustes de configuración están codificados en plantillas (por ejemplo, por una directiva o un atributo de argumento del controlador), ya no puede reutilizar esa plantilla para nada más que ese único uso. Pronto querrás volver a usar esa plantilla, pero con una configuración diferente y ahora para hacerlo, estarás preprocesando las plantillas para inyectar variables antes de que pase a angular o usando directivas masivas para escupir gigantes bloques de HTML para que pueda reutilizar todo el HTML del controlador, excepto el contenedor wrap div y sus argumentos. Para proyectos pequeños no es gran cosa. Para algo grande (en lo que sobresale angular), se pone feo rápidamente.

La alternativa: módulos

Este tipo de configuración es lo que los módulos fueron diseñados para manejar. En muchos tutoriales angulares, las personas tienen un solo módulo para toda su aplicación, pero realmente el sistema está diseñado y es totalmente compatible con muchos módulos pequeños, cada uno de los cuales envuelve piezas pequeñas de la aplicación total. Idealmente, los controladores, módulos, etc. se declararían en archivos separados y se unirían en fragmentos específicos reutilizables. Cuando su aplicación está diseñada de esta manera, obtiene una gran cantidad de reutilización además de argumentos fáciles de controlador.

El siguiente ejemplo tiene 2 módulos, reutilizando el mismo controlador, pero cada uno con su propia configuración. Esa configuración se pasa mediante inyección de dependencia mediante module.value. Esto se adhiere a la forma angular porque tenemos lo siguiente: inyección de dependencia del constructor, código de controlador reutilizable, plantillas de controlador reutilizables (el div del controlador podría incluirse fácilmente con ng-include), sistema fácilmente comprobable por unidad sin HTML y, por último, reutilizable módulos como el vehículo para unir las piezas.

Aquí hay un ejemplo:

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

Véalo en acción: jsFiddle .

Nucleon
fuente
Ya he votado, pero quiero expresar mi agradecimiento por su contribución. Esta respuesta me puso en un mejor camino. stackoverflow es mejor cuando las personas comparten las mejores prácticas y no solo fragmentos hacky. esto es brillante: "la plantilla nunca debe dictar la configuración, que es inherentemente lo que las personas desean cuando quieren pasar argumentos a los controladores desde un archivo de plantilla". gracias por tener visión láser en el meollo del asunto.
pesófago
15

Al igual que @akonsu y Nigel Findlater sugieren, se puede leer la URL donde URL es index.html#/user/:idcon $routeParams.idy utilizarlo dentro del controlador.

tu aplicación:

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

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

el servicio de recursos

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

el controlador

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

entonces, elmes accesible en la vista dependiendo de id.

François Romain
fuente
8

Si ng-initno es para pasar objetos $scope, siempre puede escribir su propia directiva. Entonces, esto es lo que obtuve:

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>

Pero mi enfoque solo puede modificar objetos, que ya están definidos en el controlador.

Sergey Goliney
fuente
Como referencia, esto funciona muy bien, pero en la versión actual de angular es $ attrs (vs attrs)
Dinis Cruz
8

Parece que la mejor solución para usted es en realidad una directiva. Esto le permite seguir teniendo su controlador, pero definir propiedades personalizadas para él.

Use esto si necesita acceso a variables en el ámbito de ajuste:

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 esto si no necesita acceso a variables en el ámbito de ajuste:

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>
btesser
fuente
7

Encontré variables de paso de $ routeProvider útiles.

Por ejemplo, utiliza un controlador MyController para múltiples pantallas, pasando algunas variables muy importantes "mySuperConstant" dentro.

Usa esa estructura simple:

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

    }
Dmitri Algazin
fuente
6

Puede hacerlo al configurar las rutas para, por ejemplo,

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

Y luego úsalo como

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

Entonces, cuando configuramos la ruta que enviamos: itemType y la recuperamos más tarde de $ routeParams.

ssaini
fuente
3

Esta pregunta es antigua, pero luché durante mucho tiempo tratando de obtener una respuesta a este problema que funcionara para mis necesidades y no la encontré fácilmente. Creo que mi siguiente solución es mucho mejor que la actualmente aceptada, quizás porque angular ha agregado funcionalidad desde que esta pregunta se planteó originalmente.

Respuesta corta, el uso del método Module.value le permite pasar datos a un constructor de controladores.

Mira mi saqueador aquí

Creo un objeto modelo, luego lo asocio con el controlador del módulo, haciendo referencia al nombre 'modelo'

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>

El constructor en mi controlador acepta un parámetro con ese mismo identificador 'modelo' al que luego puede acceder.

Controlador

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

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

Notas:

Estoy usando la inicialización manual de bootstrapping , que me permite inicializar mi modelo antes de enviarlo a angular. Esto funciona mucho mejor con el código existente, ya que puede esperar para configurar sus datos relevantes y solo compilar el subconjunto angular de su aplicación cuando lo desee.

En el plunker, agregué un botón para alertar los valores del objeto modelo que se definió inicialmente en JavaScript y se pasó a angular, solo para confirmar que angular realmente hace referencia al objeto modelo, en lugar de copiarlo y trabajar con una copia.

En esta línea:

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

Estoy pasando el objeto MyController a la función Module.controller, en lugar de declarar como una función en línea. Creo que esto nos permite definir mucho más claramente nuestro objeto controlador, pero la documentación angular tiende a hacerlo en línea, así que pensé que debería aclararse.

Estoy usando la sintaxis "controlador como" y asignando valores a la propiedad "this" de MyController, en lugar de usar la variable "$ scope". Creo que esto también funcionaría bien usando $ scope, la asignación del controlador se vería así:

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

y el constructor del controlador tendría una firma como esta:

function MyController ($scope, model) {

Si por alguna razón quisiera, también podría adjuntar este modelo como un valor de un segundo módulo, que luego adjuntará como una dependencia a su módulo primario.

Creo que su solución es mucho mejor que la actualmente aceptada porque

  1. El modelo pasado al controlador es en realidad un objeto javascript, no una cadena que se evalúa. Es una verdadera referencia al objeto y sus cambios afectan otras referencias a este objeto modelo.
  2. Angular dice que el uso de ng-init de la respuesta aceptada es un mal uso, lo que esta solución no hace.

La forma en que Angular parece funcionar en la mayoría de los otros ejemplos que he visto hace que el controlador defina los datos del modelo, lo que nunca tuvo sentido para mí, no hay separación entre el modelo y el controlador, eso realmente no parece MVC para mi. Esta solución le permite realmente tener un objeto modelo completamente separado que pasa al controlador. Además, si utiliza la directiva ng-include, puede colocar todos sus html angulares en un archivo separado, separando completamente la vista del modelo y el controlador en piezas modulares separadas.

O. invierno
fuente
2

Si usa el enrutador angular-ui, entonces esta es la solución correcta: https://github.com/angular-ui/ui-router/wiki#resolve

Básicamente, declara un conjunto de dependencias para "resolver" antes de instanciar el controlador. Puede declarar dependencias para cada uno de sus "estados". Estas dependencias se pasan luego en el "constructor" del controlador.

Juan
fuente
1

Una forma de hacerlo sería tener un servicio separado que se pueda usar como un 'recipiente' para aquellos argumentos en los que son miembros de datos públicos.

Marcin Wyszynski
fuente
La mejor respuesta, OMI. Actualmente también utilizo el almacenamiento del navegador en algunos casos para aquellos usuarios que se atreven a presionar F5, por lo que este estado se mantiene.
Lirón
1

Realmente no me gustó ninguna de las soluciones aquí para mi caso de uso particular, así que pensé que publicaría lo que hice porque no lo vi aquí.

Simplemente quería usar un controlador más como una directiva, dentro de un ciclo ng-repeat:

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

Ahora, para acceder a la objParametercreación en cada DirectivaLikeController (o para obtener el objParameter actualizado en CUALQUIER momento), todo lo que necesito hacer es inyectar $ scope y llamar $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); 
});

El único inconveniente real que veo es que requiere que el controlador principal sepa que se nombra el parámetro objParameter.

JohnTD
fuente
0

No, no es posible. Creo que puedes usar ng-init como hack http://docs.angularjs.org/api/ng.directive:ngInit .

SunnyShah
fuente
44
Le ruego que difiera, pero puede lograr esto usando ng-init y usando una función de inicio.
Jigar Patel
una palabra de advertencia: si está intentando vincularse a un valor de ámbito, o realmente hace algo que espera que el valor esté presente en la función del controlador, ya está ejecutando la función del controlador antes de que ng-initocurra, consulte plnkr.co/edit / donCm6FRBSX9oENXh9WJ? p = vista previa
drzaus
Sí, es posible. Como mínimo, usaría parámetros de datos o lo que sea. Pero definitivamente es posible.
Juan
0

Aquí hay una solución (basada en la sugerencia de Marcin Wyszynski) que funciona donde desea pasar un valor a su controlador pero no está declarando explícitamente el controlador en su html (que ng-init parece requerir), si, por ejemplo, está renderizando sus plantillas con ng-view y declarando cada controlador para la ruta correspondiente a través de 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>

En esta solución, CurrentUser es un servicio que se puede inyectar en cualquier controlador, con la propiedad .name disponible.

Dos notas:

  • Un problema que he encontrado es que .name se configura después de que se carga el controlador, por lo que como solución temporal tengo un breve tiempo de espera antes de mostrar el nombre de usuario en el alcance del controlador. ¿Hay alguna forma ordenada de esperar hasta que .name se haya configurado en el servicio?

  • Esto se siente como una manera muy fácil de obtener un usuario actual en su aplicación Angular con toda la autenticación mantenida fuera de Angular. Podría tener un before_filter para evitar que los usuarios no registrados accedan al html en el que su aplicación Angular está iniciada, y dentro de ese html simplemente podría interpolar el nombre del usuario conectado e incluso su ID si desea interactuar con los detalles del usuario a través de solicitudes http desde su aplicación Angular. Puede permitir que los usuarios no registrados utilicen la aplicación Angular con un 'usuario invitado' predeterminado. Cualquier consejo sobre por qué este enfoque sería malo sería bienvenido, ¡es demasiado fácil ser sensible!)

Ollie HM
fuente
0

Esta es una expansión de la excelente respuesta de @Michael Tiller . Su respuesta funciona para inicializar variables desde la vista inyectando el $attrsobjeto en el controlador. El problema se produce si se llama al mismo controlador $routeProvidercuando se navega por enrutamiento. Entonces se produce un error en el inyector Unknown provider : $attrsProviderporque $attrssolo está disponible para inyección cuando se compila la vista. La solución es pasar la variable (foo) $routeParamsal inicializar el controlador desde la ruta y $attrsal inicializar el controlador desde la vista. Aquí está mi solución.

De la ruta

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

Esto maneja una URL como '/mypage/bar'. Como puede ver, foo se pasa por url param y proporcionamos $injectorun objeto en blanco para $attrsque no haya errores en el inyector.

De la vista

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

</div>

Ahora el controlador

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'

});
Moisés Machua
fuente