Чи можете ви передавати параметри контролеру 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 %}друкується ідентифікатор, надісланий з сервера. але я отримую помилки.

Який правильний спосіб передавати значення в контролер при його створенні?


6
якби у вас був ідентифікатор як частина URL, ви можете просто прочитати URL
akonsu

У мене була дуже схожа проблема, і я вирішив її, як виклав у своїй відповіді. Іноді за допомогою бібліотек ми не помічаємо простої основної концепції виклику функцій JavaScript.
Jigar Patel

@nickponline Після 21+ ви все ще вважаєте, що це неможливо?
om471987

Відповіді:


362

Примітки:

Ця відповідь стара. Це лише доказ концепції про те, як можна досягти бажаного результату. Однак, можливо, це не найкраще рішення, як показано в коментарях нижче. У мене немає жодної документації, яка би підтримувала або відхиляла такий підхід. Будь ласка, зверніться до деяких коментарів нижче для подальшого обговорення цієї теми.

Оригінальний відповідь:

Я відповів на це Так, ви абсолютно можете це зробити, використовуючи ng-initпросту функцію init.

Ось приклад цього на 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)
  };


});

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
Це хороше рішення, яке працює з існуючим механізмом впорскування конструктора. Ви, по суті, створюєте два простих сервіси під назвою "ім'я" та "id". Інжектор піклується про їх узгодження під час будівництва. Дивіться: розділ « Ціннісні рецепти» docs.angularjs.org/guide/providers
Todd

1
Приємно. Зауважте, це працює і з об'єктами, а не тільки з примітивними типами, такими як рядки.
jbustamovej

Мені подобається це рішення краще. Мені вдалося модулювати свої дублювані коди за допомогою передачі параметрів. Ура!
agentpx

Це виглядає як самий ідіоматичний спосіб, але я не впевнений, що рекомендує команда кутових
VinGarcia

Це по суті встановлює глобальні пари ключ / значення? Чи можна їх віднести до певних екземплярів контролера, щоб він був підхоплений лише дочірніми контролерами під основним? Якщо ні, то здається, що це насправді не сильно відрізняється від встановлення звичайної глобальної змінної Javascript на рівні вікна.
jpierson

16

Перегляд не повинен диктувати конфігурацію

У Angular шаблоні ніколи не слід диктувати конфігурацію, що по суті є тим, чого люди хочуть, коли хочуть передавати аргументи контролерам з файлу шаблону. Це стає слизьким схилом. Якщо налаштування конфігурації жорстко закодовані в шаблонах (наприклад, атрибутом аргументу директиви чи контролера), ви більше не можете використовувати цей шаблон ні для чого, крім одного використання. Незабаром ви захочете повторно використовувати цей шаблон, але з іншим конфігурацією, і тепер для цього вам доведеться або попередньо обробляти шаблони для введення змінних до того, як вони перейдуть до кутових, або використовувати масивні директиви, щоб виплюнути гігант блоки HTML, щоб ви повторно використовували весь HTML-контролер, за винятком оболонки div та його аргументів. Для малих проектів - це не велика справа. Для чогось великого (що кут перевершує), воно стає некрасивим швидко.

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

Цей тип конфігурації - це те, що модулі були розроблені для обробки. У багатьох кутових навчальних посібниках люди мають єдиний модуль для всього свого застосування, але насправді система розроблена і повністю підтримує безліч маленьких модулів, кожен з яких обертає невеликі шматочки загальної програми. В ідеалі контролери, модулі тощо оголошуватимуться в окремих файлах і зшиваються в конкретні шматки, які можна повторно використовувати. Коли ваша програма створена таким чином, ви отримуєте безліч повторного використання, крім простих аргументів контролера.

У наведеному нижче прикладі є 2 модулі, які використовують повторно один і той же контролер, але кожен має власні налаштування конфігурації. Ці параметри конфігурації передаються за допомогою введення залежності, використовуючи module.value. Це дотримується кутового способу, оскільки у нас є наступне: введення залежності залежності від конструктора, код контролера для багаторазового використання, шаблони контролера для багаторазового використання (дивіру контролера можна легко включити в 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>

Але мій підхід може змінювати лише об'єкти, які вже визначені в контролері.


Для довідки це чудово працює, але в поточній версії кутового - $ attrs (проти attrs)
Дініс Крус

8

Схоже, найкращим рішенням для вас є насправді директива. Це дозволяє вам все ще мати свій контролер, але визначати власні властивості для нього.

Використовуйте це, якщо вам потрібен доступ до змінних в обгортковій області:

angular.module('myModule').directive('user', function ($filter) {
  return {
    link: function (scope, element, attrs) {
      $scope.connection = $resource('api.com/user/' + attrs.userId);
    }
  };
});

<user user-id="{% id %}"></user>

Використовуйте це, якщо вам не потрібен доступ до змінних в області обгортки:

angular.module('myModule').directive('user', function ($filter) {
  return {
    scope: {
      userId: '@'
    },
    link: function (scope, element, attrs) {
      $scope.connection = $resource('api.com/user/' + scope.userId);
    }
  };
});

<user user-id="{% id %}"></user>

7

Я знайшов передачі змінних з $ routeProvider корисними.

Наприклад, ви використовуєте один контролер MyController для декількох екранів, передаючи якусь дуже важливу змінну "mySuperConstant" всередині.

Використовуйте просту структуру:

Router:

$routeProvider
            .when('/this-page', {
                templateUrl: 'common.html',
                controller: MyController,
                mySuperConstant: "123"
            })
            .when('/that-page', {
                templateUrl: 'common.html',
                controller: MyController,
                mySuperConstant: "456"
            })
            .when('/another-page', {
                templateUrl: 'common.html',
                controller: MyController,
                mySuperConstant: "789"
            })

MyController:

    MyController: function ($scope, $route) {
        var mySuperConstant: $route.current.mySuperConstant;
        alert(mySuperConstant);

    }

6

Це можна зробити під час налаштування маршрутів, наприклад

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

А пізніше використовувати його як

(function () {
  'use strict';

  angular
    .module('myApp')
    .controller('NewEditItemController', NewEditItemController);

  NewEditItemController.$inject = ['$http','isEditMode',$routeParams,];

  function NewEditItemController($http, isEditMode, $routeParams) {
    /* jshint validthis:true */

    var vm = this;
    vm.isEditMode = isEditMode;
    vm.itemType = $routeParams.itemType;
  }
})();

Тож ось, коли ми налаштовували нам відправлений маршрут: itemType та повернемо його пізніше з $ routeParams.



3

Це питання давнє, але я довго боровся, намагаючись отримати відповідь на цю проблему, яка би спрацювала на мої потреби та не легко її знайшла. Я вважаю, що моє наступне рішення набагато краще, ніж прийняте на даний момент, можливо, тому, що кутова додала функціональність, оскільки це питання було спочатку задано.

Коротка відповідь, використовуючи метод Module.value, дозволяє передавати дані в конструктор контролера.

Подивіться тут мого викрадача

Я створюю об'єкт моделі, потім пов'язую його з контролером модуля, посилаючись на ім'я 'модель'

HTML / JS

  <html>
  <head>
    <script>
      var model = {"id": 1, "name":"foo"};

      $(document).ready(function(){
        var module = angular.module('myApp', []);
        module.value('model', model);
        module.controller('MyController', ['model', MyController]);
        angular.bootstrap(document, ['myApp']);
      });

      function confirmModelEdited() {
        alert("model name: " + model.name + "\nmodel id: " + model.id);
      }
    </script>

  </head>
  <body >
      <div ng-controller="MyController as controller">
        id: {{controller.model.id}} <br>
        name: <input ng-model="controller.model.name"/>{{controller.model.name}}
        <br><button ng-click="controller.incrementId()">increment ID</button>
        <br><button onclick="confirmModelEdited()">confirm model was edited</button>
    </div>
  </body>

</html>

Конструктор у моєму контролері потім приймає параметр із тією самою моделлю "модель", до якої він може отримати доступ.

Контролер

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

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

Примітки:

Я використовую вручну ініціалізацію завантажувального завантаження , що дозволяє мені ініціалізувати свою модель, перш ніж надсилати її на кутовий. Це відіграє набагато приємніше з наявним кодом, оскільки ви можете дочекатися налаштування відповідних даних та лише скласти кутовий підмножина свого додатка на вимогу, коли захочете.

У планку я додав кнопку для попередження значень об’єкта моделі, який спочатку був визначений у JavaScript та переданий у кутовий, просто для підтвердження того, що кутовий справді посилається на об'єкт моделі, а не копіює його та працює з копією.

У цьому рядку:

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

Я передаю об’єкт MyController у функцію Module.controller, а не декларую як функцію вбудованої. Я думаю, що це дозволяє нам значно чіткіше визначити наш об’єкт контролера, але кутова документація має тенденцію робити це вбудовано, тому я подумав, що він має уточнення.

Я використовую синтаксис "контролер як" і призначаю значення властивості "це" MyController, а не використовую змінну "$ range". Я вважаю, що це буде добре працювати, використовуючи параметр $ range так само, призначення контролера буде виглядати приблизно так:

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

В основному, ви оголошуєте набір залежностей, які потрібно "вирішити", перш ніж контролер буде ініційований. Ви можете оголосити залежності для кожного свого "штату". Ці залежності потім передаються в "конструкторі" контролера.


1

Одним із способів цього є надання окремої послуги, яка може використовуватися як "судно" для тих аргументів, де вони є членами загальнодоступних даних.


Найкраща відповідь, ІМО. Зараз я також використовую сховище браузера в деяких випадках для тих користувачів, які наважуються потрапити на F5, тому цей стан зберігається.
Dormouse

1

Мені не дуже сподобалося жодне з рішень тут для мого конкретного випадку використання, тому я зрозумів, що опублікую те, що робив, тому що не бачив його тут.

Я просто хотів використовувати контролер, більше схожий на директиву, в циклі ng-повтору:

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

Тепер, щоб отримати доступ до objParameterстворення під час створення кожного DirectLikeController (або отримати оновлений objParameter будь-який час), все, що мені потрібно зробити, - це ввести $ range і зателефонувати $scope.$eval('objParameter'):

var app = angular.module('myapp', []);
app.controller('DirectiveLikeController',['$scope'], function($scope) {
   //print 'a' for the 1st instance, 'b' for the 2nd instance, and 'c' for the 3rd.
   console.log($scope.$eval('objParameter').id); 
});

Єдиний реальний недолік, який я бачу, - це те, що він вимагає від батьківського контролера знати, що параметр названий objParameter.


0

Ні, це неможливо. Я думаю, ви можете використовувати ng-init як хак http://docs.angularjs.org/api/ng.directive:ngInit .


4
Я прошу відрізнятись, але ви можете досягти цього, використовуючи ng-init та використовуючи init fuction.
Jigar Patel

слово попередження - якщо ви намагаєтеся прив’язати до значення області, або дійсно зробити все, що очікує, що значення буде присутнє у функції контролера, воно вже запускає функцію контролера, перш ніж ng-initвиникає - див. plnkr.co/edit / donCm6FRBSX9oENXh9WJ? p = попередній перегляд
drzaus

Так, це можливо. Принаймні, ви б використовували параметри даних чи будь-що інше. Але це однозначно можливо.
Хуан

0

Ось рішення (засноване на пропозиції Марцина Вишинського), яке працює там, де ви хочете передати значення у свій контролер, але ви чітко не заявляєте контролер у своєму html (який, схоже, вимагає ng-init) - якщо, наприклад, ви надаєте свої шаблони з ng-переглядом і оголошуєте кожен контролер для відповідного маршруту через 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 App із усією автентифікацією, що зберігається поза Angular. Ви можете мати попередній фільтр, щоб не допустити, щоб користувачі, які не ввійшли в систему, потрапляли до html, де завантажується ваш додаток Angular, і всередині цього html ви можете просто інтерполювати ім’я користувача, що увійшов у систему, і навіть їх ідентифікатор, якщо ви хочете взаємодіяти з реквізитами користувача. через 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'

});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.