Можете ли вы передать параметры контроллеру 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 %}распечатать идентификатор, отправленный с сервера. но я получаю ошибки.

Как правильно передать значение в контроллер при его создании?

nickponline
источник
6
если бы у вас был идентификатор как часть URL, то вы могли бы просто прочитать URL
akonsu
У меня была очень похожая проблема, и я решил ее, как я написал в своем ответе. Иногда, используя библиотеки, мы упускаем из виду простую фундаментальную концепцию вызова функций JavaScript.
Джигар Патель
@nickponline После 21+ вы все еще думаете, что это невозможно?
om471987

Ответы:

362

Ноты:

Этот ответ старый. Это просто подтверждение концепции того, как можно достичь желаемого результата. Тем не менее, это может быть не лучшим решением в соответствии с некоторыми комментариями ниже. У меня нет никакой документации, чтобы поддержать или отклонить следующий подход. Пожалуйста, обратитесь к некоторым комментариям ниже для дальнейшего обсуждения этой темы.

Оригинальный ответ:

Я ответил на это Да, вы можете сделать это, используя ng-initпростую функцию инициализации.

Вот пример этого на плункере

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
Спасибо за это - избавил меня от многих царапин на голове, и это отлично сработало для моей ситуации. Мне пришлось инициализировать мой контроллер с идентификатором объекта, и это было просто правильно.
Масонуаз
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.
Сергей Голиней
8
Ответ проясняет, что документ рекомендует против такого подхода, но может ли кто-нибудь указать мне, где документы предоставляют официальное решение?
Майкл Пелл
53
Ну, я думаю, что документы плохие, чтобы просто рекомендовать этот подход без объяснения причин. Я думаю, что это блестящий подход. Если авторы фреймворка не хотят, чтобы фреймворк использовался для них «неправильным» способом, то они не должны делать возможным его использование «неправильным» способом ... и давать указания относительно правильно!
Шон де Мокрый
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
Это очень разумное решение проблемы создания гибридного приложения.
Сверхсветовой
2
@Neil: вы должны структурировать свой объект json, а затем проанализировать его внутри контроллера. Не лучшее решение, но оно может сработать. Решение Майкла отлично подходит для строковых параметров ...
M'sieur Toph '
1
это на самом деле более надежно, чем использование ng-init- если вы пытаетесь привязать к значению области действия, он уже запускает функцию контроллера до того, как это ng-initпроизойдет - см. plnkr.co/edit/donCm6FRBSX9oENXh9WJ?p=preview
drzaus
2
Я не получаю повторного изобретения колеса с директивами и т. Д. Если у вас есть контроллер, который вы хотите использовать в нескольких представлениях с несколькими различными параметрами инициализации, это самое простое и надежное решение.
Эрик Х.
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
Это хорошее решение, которое работает с существующим механизмом инжекции конструктора. По сути, вы создаете две простые службы, называемые «имя» и «идентификатор». Инжектор заботится о сопоставлении их по имени во время строительства. См. Раздел «Рецепт ценности» сайта docs.angularjs.org/guide/providers
Тодд,
1
Ницца. Обратите внимание, что это работает и с объектами, а не только с примитивными типами, такими как строки.
Jbustamovej
Мне больше нравится это решение. Я был в состоянии модулировать мои дублированные коды с помощью передачи параметров. Ура!
agentpx
Это выглядит как самый идиоматичный способ, но я не уверен, что его рекомендует угловая команда
VinGarcia
Это по существу устанавливает глобальные пары ключ / значение? Можно ли их привязать к конкретным экземплярам контроллеров, чтобы они могли быть обнаружены только на дочерних контроллерах под основным? Если нет, то похоже, что это не сильно отличается от установки обычной глобальной переменной Javascript на уровне окна.
jpierson
16

Представление не должно диктовать конфигурацию

В Angular шаблон никогда не должен диктовать конфигурацию, которая изначально присуща людям, которые хотят передавать аргументы контроллерам из файла шаблона. Это становится скользким спуском. Если параметры конфигурации жестко запрограммированы в шаблонах (например, с помощью директивы или атрибута аргумента контроллера), вы больше не сможете повторно использовать этот шаблон для чего-либо, кроме единственного использования. Вскоре вы захотите повторно использовать этот шаблон, но с другой конфигурацией, и теперь для этого вам придется либо предварительно обработать шаблоны для ввода переменных до того, как они будут переданы в angular, либо использовать массивные директивы, чтобы выплевывать гигантские значения. блоки HTML, так что вы повторно используете весь HTML контроллера, кроме div-оболочки и его аргументов. Для небольших проектов это не имеет большого значения. Для чего-то большого (что отличает угловатость) это становится ужасно быстрым.

Альтернатива: модули

Этот тип конфигурации предназначен для обработки модулей. Во многих угловых уроках люди имеют единый модуль для всего своего приложения, но на самом деле система спроектирована и полностью поддерживает множество небольших модулей, каждый из которых охватывает небольшие части всего приложения. В идеале, контроллеры, модули и т. Д. Должны быть объявлены в отдельных файлах и сшиты вместе в определенные повторяющиеся фрагменты. Когда ваше приложение разработано таким образом, вы получаете многократное использование в дополнение к простым аргументам контроллера.

В приведенном ниже примере есть 2 модуля, повторно использующих один и тот же контроллер, но каждый со своими настройками конфигурации. Эти настройки конфигурации передаются с помощью внедрения зависимостей module.value. Это соответствует угловому подходу, потому что у нас есть следующее: внедрение зависимостей в конструктор, повторно используемый код контроллера, многократно используемые шаблоны контроллеров (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 и Найджел 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 (против 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>
btesser
источник
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.

ssaini
источник
3

Этот вопрос старый, но я долго пытался найти ответ на эту проблему, который бы работал для моих нужд, и мне было нелегко его найти. Я полагаю, что мое следующее решение намного лучше, чем принятое в настоящее время, возможно, потому что angular добавил функциональность, так как этот вопрос был первоначально поставлен.

Краткий ответ, используя метод 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» и присваиваю значения свойству «this» в MyController, а не переменную «scope». Я считаю, что это будет работать нормально, используя $ scope точно так же, назначение контроллера будет выглядеть примерно так:

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

и конструктор контроллера будет иметь такую ​​подпись:

function MyController ($scope, model) {

Если по какой-либо причине вы захотите, вы также можете присоединить эту модель в качестве значения второго модуля, который вы затем присоедините в качестве зависимости к первичному модулю.

Я считаю, что его решение намного лучше, чем в настоящее время принято, потому что

  1. Модель, передаваемая в контроллер, на самом деле является объектом javascript, а не строкой, которая оценивается. Это истинная ссылка на объект, и изменения в нем влияют на другие ссылки на этот объект модели.
  2. Angular говорит, что использование ng-init принятого ответа является неправильным использованием, чего не делает это решение.

В большинстве других примеров, которые я видел, похоже, работает Angular, когда контроллер определяет данные модели, что для меня никогда не имело смысла, нет разделения между моделью и контроллером, что на самом деле не выглядит MVC для меня. Это решение позволяет вам действительно иметь совершенно отдельный объект модели, который вы передаете в контроллер. Также следует отметить, что если вы используете директиву ng-include, вы можете поместить все ваши угловые html в отдельный файл, полностью разделив представление модели и контроллер на отдельные модульные части.

О. Зима
источник
2

При использовании angular-ui-router это правильное решение: https://github.com/angular-ui/ui-router/wiki#resolve

По сути, вы объявляете набор зависимостей для «разрешения» до того, как будет создан экземпляр контроллера. Вы можете объявить зависимости для каждого из ваших «состояний». Эти зависимости затем передаются в «конструктор» контроллера.

Juan
источник
1

Один из способов сделать это - иметь отдельный сервис, который можно использовать как «сосуд» для тех аргументов, когда они являются открытыми членами данных.

Марчин Вышински
источник
Лучший ответ, ИМО. В настоящее время я также использую хранилище браузера в некоторых случаях для тех пользователей, которые осмеливаются нажать F5, поэтому это состояние сохраняется.
соня
1

Мне не очень понравились какие-либо решения здесь для моего конкретного случая использования, поэтому я решил опубликовать то, что я сделал, потому что я не видел его здесь.

Я просто хотел использовать контроллер больше как директиву внутри цикла ng-repeat:

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

Теперь, чтобы получить доступ к objParameteron on внутри каждого DirectiveLikeController (или получить актуальный 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.

JohnTD
источник
0

Нет, это невозможно. Я думаю, что вы можете использовать ng-init как взломать http://docs.angularjs.org/api/ng.directive:ngInit .

SunnyShah
источник
4
Я позволю себе не согласиться, но вы можете сделать это, используя ng-init и используя функцию init.
Джигар Патель
предупреждающее слово - если вы пытаетесь привязать значение области или действительно делаете что-либо, ожидающее, что значение будет присутствовать в функции контроллера, он уже запустил функцию контроллера прежде, чем 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. У вас может быть файл before_filter, чтобы предотвратить вход незарегистрированных пользователей в html, где загружается ваше приложение Angular, и внутри этого html вы можете просто интерполировать имя зарегистрированного пользователя и даже его ID, если вы хотите взаимодействовать с его данными. через http запросы от вашего приложения Angular. Вы можете разрешить не зарегистрированным пользователям использовать приложение Angular с гостевым пользователем по умолчанию. Любой совет о том, почему этот подход был бы плохим, был бы желателен - это слишком легко, чтобы быть разумным!)

Олли Х.М.
источник
0

Это расширение превосходного ответа @Michael Tiller . Его ответ работает для инициализации переменных из представления путем введения $attrsобъекта в контроллер. Проблема возникает, если тот же контроллер вызывается $routeProviderпри навигации по маршруту. Тогда вы получите ошибку инжектора, Unknown provider : $attrsProviderпотому что $attrsдоступно только для инъекции, когда представление компилируется. Решение состоит в том, чтобы передать переменную (foo) $routeParamsпри инициализации контроллера из маршрута и $attrsпри инициализации контроллера из представления. Вот мое решение.

С маршрута

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

Это обрабатывает URL, такие как '/mypage/bar'. Как вы можете видеть, foo передается по параметру url, и мы предоставляем $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'

});
Моисей Мачуа
источник