Angular js init ng-model z wartości domyślnych

126

Załóżmy, że masz formularz, który zawiera wartości załadowane z bazy danych. Jak inicjalizujesz ng-model?

Przykład:

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

W moim kontrolerze $ scope.card jest początkowo niezdefiniowana. Czy jest inny sposób niż zrobienie czegoś takiego?

$scope.card = {
  description: $('myinput').val()
}
Calvin Froedge
źródło

Odpowiedzi:

136

Jest to częsty błąd w nowych aplikacjach Angular. Nie chcesz zapisywać swoich wartości w kodzie HTML na serwerze, jeśli możesz tego uniknąć. W rzeczywistości, jeśli możesz uciec od całkowitego renderowania kodu HTML przez serwer, tym lepiej.

Idealnie byłoby, gdybyś chciał wysłać szablony Angular HTML, a następnie ściągnij swoje wartości przez $ http w formacie JSON i umieść je w swoim zakresie.

Więc jeśli to możliwe, zrób to:

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

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

Jeśli absolutnie MUSISZ wyrenderować swoje wartości do kodu HTML ze swojego serwera, możesz umieścić je w zmiennej globalnej i uzyskać do nich dostęp za pomocą $ window:

W nagłówku swojej strony napisałbyś:

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

A potem w kontrolerze otrzymasz to w ten sposób:

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

Mam nadzieję że to pomogło.

Ben Lesh
źródło
4
Tak, to pomaga. Chyba jestem po prostu zszokowany, że faceci z Angular podjęli taką decyzję.
Calvin Froedge
125
Nie zdziw się ... Renderowanie HTML przenosi się z serwerów i do przeglądarki. Obecnie w JavaScript istnieją dziesiątki frameworków MVC, a dla serwera o wiele bardziej wydajne jest tylko hostowanie danych JSON / XML w aplikacjach JavaScript niż renderowanie każdej strony na serwerze. To przenosi większość pracy na maszynę klienta, zamiast poddawać się serwerowi. Nie wspominając o tym, że oszczędza przepustowość. Co więcej, możesz mieć natywną aplikację mobilną (lub cokolwiek tak naprawdę), która zużywa ten sam kod JSON przez HTTP. To jest przyszłość.
Ben Lesh
3
@blesh: Świetna odpowiedź. Wielkie dzięki. Zgadzam się, że to jest droga naprzód i sam zacząłem przyjmować takie podejście; czy masz jakieś linki, które wspierają to twierdzenie? Martwię się tylko, że przeniesienie renderowania HTML z serwera do klienta może spowodować wolniejsze ładowanie strony, szczególnie na urządzeniach mobilnych, na których można renderować dużo HTML, np. Dla drzewa nawigacyjnego.
GFoley83
19
Nie zgadzam się, że to „błąd”. W niektórych przypadkach najlepszym rozwiązaniem jest renderowanie po stronie klienta. W innych przypadkach najlepszym rozwiązaniem jest renderowanie po stronie serwera. Możesz używać kątowego z obydwoma technikami, a renderowanie po stronie klienta nie powinno być traktowane jako „kątowy sposób”, ponieważ tak nie jest. Angular jest bardziej elastyczny.
hunterloftis
8
@blesh, z pewnością - każdy przypadek, w którym SEO jest ważne, ale koszt alternatywnych treści dla robotów indeksujących jest wyższy niż koszt implementacji angular z osadzonymi danymi - każda aplikacja, która przywiązuje bardzo dużą wagę do postrzeganej szybkości lub czasu renderowania dla wrażenia użytkownika - wiele witryn z treścią i witryn e-commerce należałoby do jednej z tych kategorii. Inżynierowie z Twittera, którzy próbowali renderowania klienta, a następnie wrócili na serwer, aby zapewnić lepsze wrażenia użytkownika, przedstawili również swoje uzasadnienie: blog.twitter.com/2012/improving-performance-twittercom
hunterloftis
236

Jeśli nie możesz przerobić swojej aplikacji, aby zrobić to, co sugeruje @blesh (ściągnij dane JSON za pomocą $ http lub $ zasób i wypełnij $ scope), możesz zamiast tego użyć ng-init :

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

Zobacz także AngularJS - atrybut wartości w wejściowym polu tekstowym jest ignorowany, gdy używany jest model ng?

Mark Rajcok
źródło
+1 dla drogi kątowej (w mniej preferowanej praktyce). Przykład: jsfiddle.net/9ymB3
John Lehmann
2
Używam Angular z formularzami internetowymi C # i uważam, że używanie ng-initjest bardzo przydatne podczas ustawiania wartości z kodu związanego / postback np <input name="phone" data-ng-model="frm.phone" data-ng-init="frm.phone= '<%: Model.Phone %>'" data-ng-pattern="/^[0-9() \-+]+$/" type="tel" required />. Trochę brzydki? Tak, ale rozwiązuje problem i rozwiązuje problem związany z integracją.
GFoley83
13
+1, elegancki! Ludzie opowiadają się za zgryźliwym i szybkim zachowaniem, ale tak wielu tutaj w SO mówi „zrób to wszystko po stronie klienta”, podobnie jak miliony połączeń HTTP są dobre dla zgrabnych witryn. Umieszczanie po stronie serwera danych w szablonach umożliwia strategie buforowania. Statyczny lub dynamiczny / częściowy z Memcached. To właśnie robi Twitter. Czasami jest to przydatne, innym razem nie. Chciałem to podkreślić. Po prostu musiałem to dodać, nie żebyś powiedział inaczej, @Mark.
oma,
1
Właściwie to cofam, to działa najlepiej dla mnie, super: stackoverflow.com/a/20522955/329367
Darren
1
FWIW: Nie lubię przypisywania zmiennych w wyrażeniach szablonowych, ponieważ nie jest to testowalne.
Ben Lesh
60

Jest to oczywiście brakująca, ale łatwa do dodania poprawka dla AngularJS. Po prostu napisz szybką dyrektywę, aby ustawić wartość modelu z pola wejściowego.

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

Oto moja wersja:

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);
      }
    ]
  };
});
Kevin Stone
źródło
Niezła, szybka dyrektywa. Czy jest jakiś dobry powód, aby nie pakować tego do modelu ng, aby value = i ng-model = mogły ze sobą współpracować, tak jak by się spodziewała większość ludzi?
hunterloftis
Nieźle! To jest przydatne.
santiagobasulto
3
Świetna odpowiedź! Właśnie dodałem „poprawkę”, aby to działało również z obszarami tekstowymi
Ryan Montgomery
dlaczego zdefiniowałeś controllerdla swojej dyrektywy i dlaczego nie użyłeś tej linkmetody ?. Jestem nowicjuszem w angularjs
ebram khalil
1
trzeba było zmienić val = $attrs.ngInitial || $attrs.value || $element.val()na, aby działał z wybranymi elementami.
fjsj
12

IMHO najlepszym rozwiązaniem jest dyrektywa @Kevin Stone, ale musiałem ją zaktualizować, aby działała w każdych warunkach (np. Select, textarea), a ten na pewno działa:

    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
źródło
A co z wyborem?
jwize
7

Możesz użyć niestandardowej dyrektywy (z obsługą textarea, select, radio i checkbox), sprawdź ten post na blogu https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form- pola-atrybuty / .

user1519240
źródło
1
To świetny post. Moją ulubioną odpowiedzią, ponieważ pytanie było ustawienie wartości wejścia zgodnie z wartościami inicjałów.
RPDeshaies
Stworzyłem Plnkr, aby przetestować ten kod i działa świetnie: plnkr.co/edit/ZTFOAc2ZGIZr6HjsB5gH?p=preview
RPDeshaies
6

Możesz również użyć w swoim kodzie HTML: ng-init="card.description = 12345"

Nie jest to zalecane przez Angular i jak wspomniano powyżej, powinieneś używać wyłącznie swojego kontrolera.

Ale to działa :)

koxon
źródło
4

Mam proste podejście, ponieważ w moich formularzach mam kilka ciężkich walidacji i masek. Użyłem więc jquery, aby ponownie uzyskać moją wartość i uruchomić zdarzenie „change” na walidacje:

$('#myidelement').val('123');
$('#myidelement').trigger( "change");
Guilherme Redmer Machado
źródło
3

Jak wskazywali inni, nie jest dobrą praktyką inicjowanie danych w widokach. Zalecane jest jednak zainicjowanie danych na kontrolerach. (patrz http://docs.angularjs.org/guide/controller )

Więc możesz pisać

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

i

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

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

W ten sposób widoki nie zawierają danych, a sterownik inicjalizuje wartość podczas ładowania wartości rzeczywistych.

Jkarttunen
źródło
3

Jeśli podoba Ci się podejście Kevina Stone'a powyżej https://stackoverflow.com/a/17823590/584761, rozważ łatwiejsze podejście, pisząc dyrektywy dla określonych tagów, takich jak „dane wejściowe”.

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

Jeśli wybierzesz tę trasę, nie będziesz musiał się martwić dodawaniem ng-initial do każdego tagu. Automatycznie ustawia wartość modelu na atrybut value tagu. Jeśli nie ustawisz atrybutu wartości, domyślnie będzie to pusty ciąg.

koder
źródło
3

Oto podejście skoncentrowane na serwerze:

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

Jest to ta sama podstawowa idea, co w bardziej popularnych odpowiedziach na to pytanie, z wyjątkiem $ include.value rejestruje usługę, która zawiera domyślne wartości.

Więc na serwerze mógłbyś mieć coś takiego:

{
    description: "Visa-4242"
}

I umieść go na swojej stronie za pomocą wybranej przez siebie technologii po stronie serwera. Oto streszczenie: https://gist.github.com/exclsr/c8c391d16319b2d31a43

exclsr
źródło
1
O ile wiem, jest to najbardziej angularna metoda radzenia sobie z problemem, jeśli wykonanie początkowego żądania $ http nie wchodzi w grę. Ma również tę zaletę, że jest znacznie łatwiejszy do zmodyfikowania, jeśli chcesz później zamienić na $ http. Jednym z możliwych ulepszeń jest zwrot obietnicy zamiast tego, aby uczynić wymianę jeszcze łatwiejszą. Bardzo bym się unikał ustawiania zmiennych globalnych lub używania tutaj ng-initial.
Richard
1

Ten jest bardziej ogólną wersją pomysłów wymienionych powyżej ... Po prostu sprawdza, czy w modelu jest jakaś wartość, a jeśli nie, ustawia wartość na model.

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 (przykład użycia):

<select class="form-control"
        ng-options="v for (i, v) in compressionMethods"
        ng-model="action.parameters.Method"
        default-value="'LZMA2'"></select>
Eitan HS
źródło
To zadziałało dla mnie, ale dopiero po przekazaniu $scopewezwania getter()- mam nadzieję, że to wyjaśni wszystko dla każdego, kto spróbuje tego!
zesda 13.07.17
0

Wypróbowałem to, co zasugerował @Mark Rajcok. Działa dla wartości String (Visa-4242). Proszę, odnieś to skrzypce .

Od skrzypiec:

To samo, co robi się na skrzypcach, można zrobić za pomocą ng-repeat, co każdy mógłby polecić. Ale po przeczytaniu odpowiedzi udzielonej przez @Marka Rajcok chciałem po prostu wypróbować to samo dla formularza z szeregiem profili. Wszystko działa dobrze, dopóki nie mam $ scope.profiles = [{}, {}]; kod w sterowniku. Jeśli usunę ten kod, otrzymuję błędy. Ale w normalnych scenariuszach nie mogę drukować $scope.profiles = [{},{}]; tak, jak drukuję, ani odczytywać HTML z serwera. Czy będzie możliwe wykonanie powyższego w podobny sposób, jak @Mark Rajcok dla wartości łańcuchowych <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, bez konieczności powtarzania części JavaScript z serwera.

Rajkamal Subramanian
źródło
1
Możesz użyć ng-init do zainicjowania tablicy, a następnie użyć ng-repeat do wyprowadzenia linii formularza: jsfiddle.net/6tP6x/1
Karen Zilles
0

Właśnie dodałem obsługę wybranego elementu do „naprawy” Ryana Montgomery'ego

<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
źródło
0

Jeśli masz wartość init w adresie URL, jak na przykład mypage/id, w kontrolerze kątowego JS możesz location.pathnameznaleźć id i przypisać go do wybranego modelu.

Neon
źródło