Angular js init ng-модель из значений по умолчанию

126

Допустим, у вас есть форма, в которой значения загружены из базы данных. Как инициализировать ng-модель?

Пример:

<input name="card[description]" ng-model="card.description" value="Visa-4242">

В моем контроллере $ scope.card изначально не определен. Есть ли способ, кроме этого?

$scope.card = {
  description: $('myinput').val()
}
Кэлвин Фредж
источник

Ответы:

136

Это распространенная ошибка в новых приложениях Angular. Вы не хотите записывать свои значения в свой HTML на сервере, если можете этого избежать. На самом деле, тем лучше, если вам удастся отказаться от того, чтобы ваш сервер полностью отображал HTML.

В идеале вы хотите отправить свои шаблоны HTML Angular, а затем извлечь свои значения через $ http в JSON и поместить их в свою область видимости.

Итак, если возможно, сделайте следующее:

app.controller('MyController', function($scope, $http) {
    $http.get('/getCardInfo.php', function(data) {
       $scope.card = data;
    });
});

<input type="text" ng-model="card.description" />

Если вы ОБЯЗАТЕЛЬНО ДОЛЖНЫ отображать свои значения в HTML со своего сервера, вы можете поместить их в глобальную переменную и получить к ним доступ с помощью $ window:

В заголовке своей страницы вы должны написать:

<head>
   <script>
       window.card = { description: 'foo' };
   </script>
</head>

И тогда в вашем контроллере вы получите это так:

app.controller('MyController', function($scope, $window) {
   $scope.card = $window.card;
});

Надеюсь, это поможет.

Бен Леш
источник
4
Да, это помогает. Думаю, я просто шокирован тем, что ребята из Angular приняли такое решение.
Calvin Froedge
125
Не удивляйтесь ... Рендеринг HTML переходит с серверов в браузер. В наши дни в JavaScript существуют десятки фреймворков MVC, и для сервера гораздо эффективнее просто размещать данные JSON / XML в приложениях JavaScript, чем отображать каждую страницу на сервере. Это перекладывает большую часть работы на клиентскую машину, а не на сервер. Не говоря уже об экономии на пропускной способности. В довершение ко всему, у вас может быть собственное мобильное приложение (или что-то еще), которое использует тот же JSON через HTTP. Это будущее.
Бен Леш
3
@blesh: Отличный ответ. Большое спасибо. Я согласен с тем, что это путь вперед, и сам начал применять этот подход; у вас есть ссылки, подтверждающие это утверждение? Я просто также обеспокоен тем, что перемещение рендеринга html с сервера на клиент может привести к более медленному времени загрузки страницы, особенно на мобильных устройствах, где вы можете отображать много HTML, например, для дерева навигации.
GFoley83 01
19
Я не согласен с тем, что это «ошибка». В некоторых случаях рендеринг на стороне клиента - лучшее решение. В других случаях лучше всего подходит рендеринг на стороне сервера. Вы можете использовать angular с обоими методами, и рендеринг на стороне клиента не следует воспринимать как «угловой способ», потому что это не так. Angular более гибкий, чем это.
hunterloftis
8
@blesh, конечно - любой случай, когда SEO важен, но стоимость альтернативного контента для поисковых роботов выше, чем стоимость внедрения angular со встроенными данными - любого приложения, которое очень высоко ценит воспринимаемую скорость или время отрисовки для пользовательский опыт - многие информационные сайты и сайты электронной коммерции попали бы в одну из этих категорий. Инженеры Twitter, которые попробовали рендеринг клиента, а затем вернулись на сервер, чтобы улучшить взаимодействие с пользователем, также изложили свои соображения: blog.twitter.com/2012/improving-performance-twittercom
hunterloftis
236

Если вы не можете переработать свое приложение, чтобы оно выполняло то, что предлагает @blesh (вытащить данные JSON с помощью $ http или $ resource и заполнить $ scope), вы можете вместо этого использовать ng-init :

<input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">

См. Также AngularJS - Атрибут Value в текстовом поле ввода игнорируется, когда используется ng-модель?

Марк Райкок
источник
+1 за угловой путь (из менее предпочтительной практики). Пример: jsfiddle.net/9ymB3
Джон Леманн,
2
Я использую Angular с веб-формами C #, и я считаю, что использование ng-initочень полезно при установке значений из кода программной части / обратной передачи, например <input name="phone" data-ng-model="frm.phone" data-ng-init="frm.phone= '<%: Model.Phone %>'" data-ng-pattern="/^[0-9() \-+]+$/" type="tel" required />. Немного некрасиво? Да, но помогает решить проблему с интеграцией.
GFoley83
13
+1, шикарно! Люди выступают за быстрое и быстрое поведение, но многие здесь, в SO, говорят: «Делайте все на стороне клиента», как и миллионы HTTP-вызовов - это хорошо для быстрых сайтов. Внесение стороны сервера данных в шаблоны позволяет использовать стратегии кэширования. Статический или динамический / частичный с Memcached. Это то, что делает твиттер. Иногда это полезно, иногда нет. Хотел это подчеркнуть. Просто пришлось добавить это, не то чтобы ты сказал иначе, @Mark.
oma
1
На самом деле, я беру это обратно, это лучше всего работает для меня, здорово: stackoverflow.com/a/20522955/329367
Даррен
1
FWIW: мне не нравится присваивание переменных в выражениях шаблона, потому что это невозможно проверить.
Бен Леш
60

Это явно недостающее, но легко добавляемое исправление для AngularJS. Просто напишите короткую директиву, чтобы установить значение модели из поля ввода.

<input name="card[description]" value="Visa-4242" ng-model="card.description" ng-initial>

Вот моя версия:

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

app.directive('ngInitial', function() {
  return {
    restrict: 'A',
    controller: [
      '$scope', '$element', '$attrs', '$parse', function($scope, $element, $attrs, $parse) {
        var getter, setter, val;
        val = $attrs.ngInitial || $attrs.value;
        getter = $parse($attrs.ngModel);
        setter = getter.assign;
        setter($scope, val);
      }
    ]
  };
});
Кевин Стоун
источник
Хорошая быстрая директива. Есть ли веская причина не упаковывать это в ng-model, чтобы value = и ng-model = могли работать вместе, как ожидает большинство людей?
hunterloftis
Довольно приятно! Это полезно.
santiagobasulto
3
Отличный ответ! Я просто добавил «исправление», чтобы это работало и с текстовыми полями
Райан Монтгомери,
почему вы определили controllerдля своей директивы и почему не использовали linkметод ?. Я новичок в angularjs
Эбрам Халил
1
необходимо изменить val = $attrs.ngInitial || $attrs.value || $element.val()на, чтобы он работал с выбранными элементами.
fjsj
12

ИМХО лучшим решением является директива @Kevin Stone, но мне пришлось обновить ее, чтобы она работала в любых условиях (например, select, textarea), и это точно работает:

    angular.module('app').directive('ngInitial', function($parse) {
        return {
            restrict: "A",
            compile: function($element, $attrs) {
                var initialValue = $attrs.value || $element.val();
                return {
                    pre: function($scope, $element, $attrs) {
                        $parse($attrs.ngModel).assign($scope, initialValue);
                    }
                }
            }
        }
    });
jtompl
источник
А как насчет select?
jwize 01
7

Вы можете использовать настраиваемую директиву (с поддержкой textarea, select, radio и checkbox), проверьте это сообщение в блоге https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form- поля-атрибуты / .

user1519240
источник
1
Это отличная статья. Мой любимый ответ, так как вопрос заключался в том, чтобы установить значение ввода в соответствии со значениями инициалов.
RPDeshaies
Я создал Plnkr для тестирования этого кода, и он отлично работает: plnkr.co/edit/ZTFOAc2ZGIZr6HjsB5gH?p=preview
RPDeshaies
6

Вы также можете использовать в своем HTML-коде: ng-init="card.description = 12345"

Это не рекомендуется Angular, и, как упоминалось выше, вы должны использовать исключительно свой контроллер.

Но работает :)

koxon
источник
4

У меня простой подход, потому что в моих формах есть тяжелые проверки и маски. Итак, я использовал jquery, чтобы снова получить свое значение и запустить событие «изменение» для проверки:

$('#myidelement').val('123');
$('#myidelement').trigger( "change");
Гильерме Редмер Мачадо
источник
3

Как указывали другие, инициализация данных в представлениях не является хорошей практикой. Однако рекомендуется инициализировать данные на контроллерах. (см. http://docs.angularjs.org/guide/controller )

Так что вы можете написать

<input name="card[description]" ng-model="card.description">

и

$scope.card = { description: 'Visa-4242' };

$http.get('/getCardInfo.php', function(data) {
   $scope.card = data;
});

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

Jkarttunen
источник
3

Если вам нравится подход Кевина Стоуна выше https://stackoverflow.com/a/17823590/584761, рассмотрите более простой подход, написав директивы для определенных тегов, таких как 'input'.

app.directive('input', function ($parse) {
    return {
        restrict: 'E',
        require: '?ngModel',
        link: function (scope, element, attrs) {
            if (attrs.ngModel) {
                val = attrs.value || element.text();
                $parse(attrs.ngModel).assign(scope, val);
            }
        }
    }; });

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

кодировщик
источник
3

Вот подход, ориентированный на сервер:

<html ng-app="project">
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    <script>
        // Create your module
        var dependencies = [];
        var app = angular.module('project', dependencies);

        // Create a 'defaults' service
        app.value("defaults", /* your server-side JSON here */);

        // Create a controller that uses the service
        app.controller('PageController', function(defaults, $scope) {
            // Populate your model with the service
            $scope.card = defaults;
        });
    </script>

    <body>
        <div ng-controller="PageController">
            <!-- Bind with the standard ng-model approach -->
            <input type="text" ng-model="card.description">
        </div>
    </body>
</html>

Это та же основная идея, что и в более популярных ответах на этот вопрос, за исключением того, что $ provide.value регистрирует службу, которая содержит ваши значения по умолчанию.

Итак, на сервере у вас может быть что-то вроде:

{
    description: "Visa-4242"
}

И разместите его на своей странице с помощью выбранной вами серверной технологии. Вот суть: https://gist.github.com/exclsr/c8c391d16319b2d31a43

exclsr
источник
1
Насколько я могу судить, это самый угловой метод решения проблемы, если первоначальный запрос $ http не подходит. Он также имеет то преимущество, что его намного проще изменить, если вы захотите позже перейти на $ http. Одно из возможных усовершенствований - возврат обещания, чтобы сделать переход еще проще. Я бы очень уклонился от установки глобальных переменных или использования здесь ng-initial.
Ричард
1

Это более общая версия идей, упомянутых выше ... Он просто проверяет, есть ли какое-либо значение в модели, а если нет, он устанавливает значение для модели.

JS:

function defaultValueDirective() {
    return {
        restrict: 'A',
        controller: [
            '$scope', '$attrs', '$parse',
            function ($scope, $attrs, $parse) {
                var getter = $parse($attrs.ngModel);
                var setter = getter.assign;
                var value = getter();
                if (value === undefined || value === null) {
                    var defaultValueGetter = $parse($attrs.defaultValue);
                    setter($scope, defaultValueGetter());
                }
            }
        ]
    }
}

HTML (пример использования):

<select class="form-control"
        ng-options="v for (i, v) in compressionMethods"
        ng-model="action.parameters.Method"
        default-value="'LZMA2'"></select>
Эйтан Х.С.
источник
Это сработало для меня, но только после перехода $scopeк вызову getter()- надеюсь, это проясняет ситуацию для всех, кто пытается это сделать!
zesda
0

Я попробовал то, что предложил @Mark Rajcok. Он работает со строковыми значениями (Visa-4242). Пожалуйста, обратитесь к этой скрипке .

Со скрипки:

То же, что и в скрипте, можно сделать и с помощью ng-repeat, что можно порекомендовать. Но после прочтения ответа @Mark Rajcok я просто хотел попробовать то же самое для формы с массивом профилей. Все работает хорошо, пока у меня не будет $ scope.profiles = [{}, {}]; код в контроллере. Если я удалю этот код, я получаю ошибки. Но в обычных сценариях я не могу печатать, $scope.profiles = [{},{}]; поскольку я печатаю или отображаю html с сервера. Можно ли будет выполнить вышеизложенное аналогично тому, как это сделал @Mark Rajcok для строковых значений, например <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, без необходимости повторять часть JavaScript с сервера.

Раджкамал Субраманиан
источник
1
Вы можете использовать ng-init для инициализации массива, а затем использовать ng-repeat для вывода строк формы: jsfiddle.net/6tP6x/1
Karen Zilles
0

Только что добавлена ​​поддержка выбора элемента в "исправить" Райана Монтгомери.

<select class="input-control" ng-model="regCompModel.numberOfEmployeeId" ng-initial>
    <option value="1af38656-a752-4a98-a827-004a0767a52d"> More than 500</option>
    <option value="233a2783-db42-4fdb-b191-0f97d2d9fd43"> Between 250 and 500</option>
    <option value="2bab0669-550c-4555-ae9f-1fdafdb872e5"> Between 100 and 250</option>
    <option value="d471e43b-196c-46e0-9b32-21e24e5469b4"> Between 50 and 100</option>
    <option value="ccdad63f-69be-449f-8b2c-25f844dd19c1"> Between 20 and 50</option>
    <option value="e00637a2-e3e8-4883-9e11-94e58af6e4b7" selected> Less then 20</option>
</select>

app.directive('ngInitial', function () {
return {
    restrict: 'A',
    controller: ['$scope', '$element', '$attrs', '$parse', function ($scope, $element, $attrs, $parse) {
        val = $attrs.sbInitial || $attrs.value || $element.val() || $element.text()
        getter = $parse($attrs.ngModel)
        setter = getter.assign
        setter($scope, val)
    }]
}

});

kolesso
источник
0

Если у вас есть значение init в URL-адресе, например mypage/id, в контроллере angular JS вы можете использовать его, location.pathnameчтобы найти идентификатор и назначить его нужной модели.

неон
источник