Você pode passar parâmetros para um controlador AngularJS na criação?

278

Eu tenho um controlador responsável pela comunicação com uma API para atualizar as propriedades de um usuário, nome, email, etc. Cada usuário tem um 'id'que é passado do servidor quando a página de perfil é exibida.

Gostaria de passar esse valor para o controlador AngularJS para que ele saiba qual é o ponto de entrada da API para o usuário atual. Eu tentei passar o valor ng-controller. Por exemplo:

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

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

e no HTML

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

onde {% id %}imprime o ID enviado do servidor. mas eu recebo erros.

Qual é a maneira correta de passar um valor para um controlador em sua criação?

nickponline
fonte
6
se você tivesse o id como parte do URL, você poderia ler o URL
akonsu
Eu tive um problema muito semelhante e resolvi-o conforme postei na minha resposta. Às vezes, usando as bibliotecas, ignoramos o conceito fundamental simples de chamada de função JavaScript.
precisa saber é o seguinte
@nickponline Depois dos 21 anos, você ainda acha que não é possível?
Om471987

Respostas:

362

Notas:

Esta resposta é antiga. Esta é apenas uma prova de conceito de como o resultado desejado pode ser alcançado. No entanto, pode não ser a melhor solução, conforme alguns comentários abaixo. Não tenho nenhuma documentação para apoiar ou rejeitar a abordagem a seguir. Consulte alguns dos comentários abaixo para uma discussão mais aprofundada sobre este tópico.

Resposta original:

Eu respondi isso para Sim, você absolutamente pode fazê-lo usando ng-inituma simples função init.

Aqui está o exemplo disso no 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
fonte
5
Obrigado por isso - me poupou muito coçar a cabeça e funcionou perfeitamente para a minha situação. Eu tive que inicializar meu controlador com um ID de objeto, e isso estava correto.
Masonoise
26
De 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.
Sergey Goliney
8
A resposta deixa claro que os documentos recomendam essa abordagem, mas alguém pode me indicar onde os documentos fornecem a solução oficial?
Michael Pell
53
Bem, acho que os documentos são ruins em apenas recomendar essa abordagem sem justificar o motivo. Eu acho que essa é uma abordagem brilhante. Se os autores da estrutura não quiserem que a estrutura seja usada da maneira "errada", eles não deverão possibilitar a utilização dessa maneira "errada" ... e fornecer orientações quanto à " caminho certo!
Shawn de Wet
2
uma palavra de aviso - se você está tentando vincular a um valor de escopo ou realmente faz alguma coisa que espera que o valor esteja presente na função do controlador, ele já executou a função do controlador antes de ng-initocorrer - consulte plnkr.co/edit / donCm6FRBSX9oENXh9WJ? p = visualização
drzaus 16/10
143

Estou muito atrasado para isso e não faço ideia se é uma boa ideia, mas você pode incluir o $attrsinjetável na função do controlador, permitindo que o controlador seja inicializado usando "argumentos" fornecidos em um elemento, por exemplo

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>

Novamente, não faço ideia se é uma boa ideia, mas parece funcionar e é outra alternativa.

Michael Tiller
fonte
3
Como eu passaria um objeto usando essa abordagem? var obj = {a: 1, b: 2};
Neil
3
Essa é uma solução muito razoável para o problema de produzir um aplicativo híbrido.
superluminary
2
@ Neil: você precisa restringir o objeto json e analisá-lo dentro do controlador. Não é a melhor solução, mas pode funcionar. A solução de Michael é adequada para parâmetros de string ...
M'sieur Toph '
1
este é realmente mais seguro do que usar ng-init- se você está tentando se ligam a um valor escopo, ele já está executar a função de controlador antes de ng-initocorrer - ver plnkr.co/edit/donCm6FRBSX9oENXh9WJ?p=preview
drzaus
2
Não recebo toda a reinvenção da roda com diretivas etc. Se você possui um controlador que deseja usar em várias visualizações com alguns parâmetros de inicialização diferentes, esta é a solução mais fácil e confiável.
21715 Eric Eric
39

Isso também 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>
Jonghee Park
fonte
3
Essa é uma boa solução que funciona com o mecanismo de injeção de construtor existente. Você está, em essência, criando dois serviços simples chamados 'nome' e 'id'. O injetor cuida de combiná-los pelo nome durante a construção. Veja: a seção Receita de valor de docs.angularjs.org/guide/providers
Todd
1
Agradável. Observe que isso também funciona com objetos, não apenas com tipos primitivos, como strings.
jbustamovej
Eu gosto mais desta solução. Consegui modularizar meus códigos duplicados por meio da passagem de parâmetros. Felicidades!
agentpx 7/09/15
Este parece ser a forma mais idiomática, mas não estou certo de que é recomendado pela equipe angular
VinGarcia
Isso essencialmente configura pares globais de chave / valor? Elas podem ter um escopo definido para determinadas instâncias do controlador, para que sejam captadas apenas nos controladores filhos abaixo do principal? Caso contrário, parece que isso não é realmente muito diferente do que apenas definir uma variável Javascript global regular no nível da janela.
jpierson
16

A visualização não deve ditar a configuração

Em Angular, o modelo nunca deve ditar a configuração, que é inerentemente o que as pessoas desejam quando desejam passar argumentos para os controladores a partir de um arquivo de modelo. Isso se torna uma ladeira escorregadia. Se as definições de configuração estiverem codificadas em modelos (como por um atributo de diretiva ou argumento do controlador), você não poderá mais reutilizá-lo para nada além desse uso único. Em breve, você desejará reutilizar esse modelo, mas com configurações diferentes e agora, para isso, você estará pré-processando os modelos para injetar variáveis ​​antes que elas passem para o angular ou usando diretivas massivas para cuspir gigantes. blocos de HTML para que você reutilize todo o HTML do controlador, exceto a div do wrapper e seus argumentos. Para pequenos projetos, não é grande coisa. Para algo grande (no que a angular se destaca), fica feio rapidamente.

A Alternativa: Módulos

Esse tipo de configuração é o que os módulos foram projetados para manipular. Em muitos tutoriais angulares, as pessoas têm um único módulo para toda a sua aplicação, mas, na verdade, o sistema é projetado e suporta totalmente muitos módulos pequenos, cada um que envolve pequenos pedaços da aplicação total. Idealmente, controladores, módulos etc. seriam declarados em arquivos separados e costurados em pedaços reutilizáveis ​​específicos. Quando seu aplicativo é projetado dessa maneira, você recebe muita reutilização, além dos argumentos fáceis do controlador.

O exemplo abaixo possui 2 módulos, reutilizando o mesmo controlador, mas cada um com suas próprias configurações. As definições de configuração são passadas via injeção de dependência usando module.value. Isso segue o caminho angular porque temos o seguinte: injeção de dependência do construtor, código do controlador reutilizável, modelos de controlador reutilizáveis ​​(a div do controlador pode ser facilmente incluída com ng-include), sistema facilmente testável por unidade sem HTML e, por fim, reutilizável módulos como o veículo para costurar as peças.

Aqui está um exemplo:

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

Veja em ação: jsFiddle .

Nucleon
fonte
Eu já votei, mas quero expressar meus agradecimentos por sua contribuição. Essa resposta me colocou em um caminho melhor. O stackoverflow é melhor quando as pessoas compartilham práticas recomendadas e não apenas snippets hacky. isto é brilhante: "o modelo nunca deve ditar a configuração, que é inerentemente o que as pessoas desejam quando desejam passar argumentos para os controladores a partir de um arquivo de modelo". obrigado por ter visão a laser no cerne da questão.
pestófago
15

Como @akonsu e Nigel Findlater sugerir, você pode ler a url onde url é index.html#/user/:idcom $routeParams.ide usá-lo dentro do controlador.

seu aplicativo:

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

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

o serviço de recursos

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

o controlador

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

então, elmé acessível na visualização, dependendo do id.

François Romain
fonte
8

Se ng-initnão é para passar objetos $scope, você sempre pode escrever sua própria diretiva. Então aqui está o que eu tenho:

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>

Mas minha abordagem só pode modificar objetos, que já estão definidos no controlador.

Sergey Goliney
fonte
Para referência isso funciona muito bem, mas na versão atual do angular é de US $ attrs (vs attrs)
Dinis Cruz
8

Parece que a melhor solução para você é realmente uma diretiva. Isso permite que você ainda tenha seu controlador, mas defina propriedades personalizadas para ele.

Use isso se precisar acessar variáveis ​​no escopo de disposição:

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 isso se você não precisar acessar variáveis ​​no escopo de disposição:

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

Eu encontrei variáveis ​​úteis de $ routeProvider úteis.

Por exemplo, você usa um controlador MyController para várias telas, passando alguma variável muito importante "mySuperConstant" para dentro.

Use essa estrutura simples:

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
fonte
6

Você pode fazer isso ao configurar as rotas para, por exemplo,

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

E depois use-o 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;
  }
})();

Então, aqui, quando configuramos a rota que enviamos: itemType e recuperamos mais tarde a partir de $ routeParams.

ssaini
fonte
3

Esta pergunta é antiga, mas lutei por um longo tempo tentando obter uma resposta para esse problema que funcionasse para minhas necessidades e não a encontrasse facilmente. Acredito que minha solução a seguir é muito melhor do que a atualmente aceita, talvez porque o angular tenha adicionado funcionalidade desde que a pergunta foi feita originalmente.

Resposta curta, usando o método Module.value permite passar dados para um construtor de controlador.

Veja meu desentupidor aqui

Crio um objeto de modelo, associo-o ao controlador do módulo, referenciando-o ao nome '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>

O construtor no meu controlador aceita um parâmetro com o mesmo identificador 'model' que ele pode acessar.

Controlador

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

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

Notas:

Estou usando a inicialização manual do bootstrapping , o que me permite inicializar meu modelo antes de enviá-lo para o angular. Isso funciona muito mais bem com o código existente, pois você pode esperar para configurar seus dados relevantes e compilar apenas o subconjunto angular do seu aplicativo sob demanda quando desejar.

No plunker, adicionei um botão para alertar os valores do objeto de modelo que foram inicialmente definidos em javascript e passados ​​para angular, apenas para confirmar que angular está realmente fazendo referência ao objeto de modelo, em vez de copiá-lo e trabalhar com uma cópia.

Nesta linha:

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

Estou passando o objeto MyController para a função Module.controller, em vez de declarar como uma função embutida. Eu acho que isso nos permite definir muito mais claramente nosso objeto de controlador, mas a documentação Angular tende a fazê-lo em linha, então achei que precisava de esclarecimentos.

Estou usando a sintaxe "controller as" e atribuindo valores à propriedade "this" do MyController, em vez de usar a variável "$ scope". Acredito que isso funcionaria bem usando $ scope também, a atribuição do controlador seria algo parecido com isto:

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

e o construtor do controlador teria uma assinatura como esta:

function MyController ($scope, model) {

Se, por qualquer motivo que você quiser, também poderá anexar esse modelo como valor de um segundo módulo, que será anexado como uma dependência ao seu módulo principal.

Eu acredito que a solução dele é muito melhor do que a atualmente aceita, porque

  1. O modelo passado para o controlador é na verdade um objeto javascript, não uma sequência que é avaliada. É uma referência verdadeira ao objeto e as alterações nele afetam outras referências a esse objeto de modelo.
  2. Angular diz que o uso do ng-init pela resposta aceita é um mau uso, o que esta solução não faz.

A maneira como o Angular parece funcionar na maioria dos outros exemplos que eu já vi tem o controlador definindo os dados do modelo, o que nunca fez sentido para mim, não há separação entre o modelo e o controlador, o que realmente não parece MVC para mim. Esta solução permite que você realmente tenha um objeto de modelo completamente separado que você passa para o controlador. Observe também que, se você usar a diretiva ng-include, poderá colocar todo o seu html angular em um arquivo separado, separando completamente a visualização e o modelo do seu modelo em partes modulares separadas.

O. Winter
fonte
2

Se estiver usando o angular-ui-router, esta é a solução correta: https://github.com/angular-ui/ui-router/wiki#resolve

Basicamente, você declara um conjunto de dependências para "resolver" antes que o controlador seja instanciado. Você pode declarar dependências para cada um dos seus "estados". Essas dependências são passadas no "construtor" do controlador.

Juan
fonte
1

Uma maneira de fazer isso seria ter um serviço separado que possa ser usado como um 'navio' para os argumentos em que eles são membros de dados públicos.

Marcin Wyszynski
fonte
Melhor resposta, IMO. Atualmente, também uso o armazenamento do navegador em alguns casos para os usuários que ousam acessar a F5, portanto, esse estado é mantido.
Dormouse
1

Eu realmente não gostei de nenhuma das soluções aqui para o meu caso de uso específico, então imaginei que postaria o que fiz porque não o vi aqui.

Eu simplesmente queria usar um controlador mais como uma diretiva, dentro de um loop ng-repeat:

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

Agora, para acessar a objParametercriação em cada DiretivoLikeController (ou obter o objParameter atualizado a QUALQUER MOMENTO), tudo o que preciso fazer é injetar $ scope e chamar $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); 
});

A única desvantagem real que vejo é que requer que o controlador pai saiba que o parâmetro está nomeado objParameter.

JohnTD
fonte
0

Não, não é possível. Eu acho que você pode usar o ng-init como hack http://docs.angularjs.org/api/ng.directive:ngInit .

SunnyShah
fonte
4
Eu imploro para diferir, mas você pode fazer isso usando ng-init e usando uma função de inicialização.
precisa saber é o seguinte
uma palavra de aviso - se você está tentando vincular a um valor de escopo ou realmente faz alguma coisa que espera que o valor esteja presente na função do controlador, ele já executou a função do controlador antes que ng-initocorra - consulte plnkr.co/edit / donCm6FRBSX9oENXh9WJ? p = visualização
drzaus 16/10
Sim, é possível. No mínimo, você usaria parâmetros de dados ou qualquer outra coisa. Mas é definitivamente possível.
316 Juan Juan
0

Aqui está uma solução (baseada na sugestão de Marcin Wyszynski) que funciona onde você deseja passar um valor para o seu controlador, mas você não está declarando explicitamente o controlador no seu html (que o ng-init parece exigir) - se, por exemplo, você está renderizando seus modelos com ng-view e declarando cada controlador para a rota correspondente 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>

Nesta solução, o CurrentUser é um serviço que pode ser injetado em qualquer controlador, com a propriedade .name disponível.

Duas notas:

  • um problema que encontrei é que .name é definido após o carregamento do controlador, portanto, como solução alternativa, tenho um tempo limite curto antes de renderizar o nome de usuário no escopo do controlador. Existe uma maneira elegante de esperar até que .name seja definido no serviço?

  • essa é uma maneira muito fácil de obter um usuário atual no seu aplicativo Angular com toda a autenticação mantida fora do Angular. Você pode ter um before_filter para impedir que usuários não logados acessem o html em que seu aplicativo Angular é inicializado e, nesse html, você pode interpolar apenas o nome do usuário conectado e até o ID deles, se desejar interagir com os detalhes do usuário. via solicitações http do seu aplicativo Angular. Você pode permitir que usuários não conectados usem o Angular App com um 'usuário convidado' padrão. Qualquer conselho sobre por que essa abordagem seria ruim seria bem-vindo - é fácil demais ser sensato!)

Ollie HM
fonte
0

Esta é uma expansão da excelente resposta de @Michael Tiller . Sua resposta funciona para inicializar variáveis ​​a partir da visualização injetando o $attrsobjeto no controlador. O problema ocorre se o mesmo controlador for chamado a partir de $routeProviderquando você navegar pelo roteamento. Então você recebe o erro do injetor, Unknown provider : $attrsProviderporque $attrssó está disponível para injeção quando a visualização é compilada. A solução é passar a variável (foo) $routeParamsao inicializar o controlador a partir da rota e $attrsao inicializar o controlador a partir da visualização. Aqui está a minha solução.

Da rota

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

Isso lida com um URL como '/mypage/bar'. Como você pode ver, o foo é passado pelo parâmetro url e fornecemos $injectorum objeto em branco para $attrsque não haja erros do injetor.

Da vista

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

</div>

Agora o 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'

});
Moses Machua
fonte