Deklaracja modelu w templatce a $scope

Zagadnął mnie kolega, przedstawiając ciekawy problem związany z Angular.js. Stworzył templatkę, w której do inputa przypięty był model, a w innym miejscu dowiązał do niego wyrażenie. Utrzymywał, że to nie działa. Po krótkiej indagacji i szukaniu problemu okazało się, że kod zawierał pewną pozornie niewinną niespodziankę, wprowadzającą ciekawe i nie do końca oczekiwane zachowanie.

To był przykładowy kod, który (rzekomo) sprawiał problemy:

<div>
    <input type="text" ng-model="x">
</div>
{{ x }}

Na pierwszy rzut oka to powinno działać (co zresztą łatwo potwierdzić empirycznie). Co zatem mogło być nie w porządku? Po krótkiej indagacji i szukaniu problemu okazało się, że kod zawierał jeszcze pozornie nic nieznaczącą dyrektywę ngIf:

<div ng-if="1">
    <input type="text" ng-model="x">
</div>
{{ x }}

No i tutaj zaczyna się ciekawa sprawa. Od razu powiem, co się dzieje. Otóż dyrektywa ngIf tworzy potomny scope, więc gdy zadeklarujemy w nim model, nie będzie on dostępny w rodzicu tegoż. Rozwiązaniem mogłoby być stworzenie zmiennej bezpośrednio na rodzicu:

<div ng-if="1">
    <input type="text" ng-model="$parent.x">
</div>
{{ x }}

W ten sposób wewnątrz potomnego scope odwołujemy się bezpośrednio do jego rodzica, dzięki czemu późniejsze wywołanie modelu jest nadal możliwe.

Natomiast ciekawy jest problem tego, w jakich sytuacjach zmienna jest dziedziczona ze scope nadrzędnego do potomnego i kiedy obustronne powiązanie między nimi będzie działało jak należy. Dla zwizualizowania sobie tematu, proponuję następujący przykład.

AppCtrl.js:

angular.module("app").controller("AppCtrl", function($scope) {
    $scope.foo = "ctrl";
    $scope.bar = { baz: "ctrl" };
});

app.html:

<div>
    <div ng-if="1">
        <div>foo: <input type="text" ng-model="foo"></div>
        <div>bar.baz: <input type="text" ng-model="bar.baz"></div>
        <div>bat: <input type="text" ng-model="bat"></div>
        <div>$parent.qux: <input type="text" ng-model="$parent.qux"></div>
    </div>

    <div>foo: <input type="text" ng-model="foo"></div>
    <div>bar.baz: <input type="text" ng-model="bar.baz"></div>
    <div>bat: <input type="text" ng-model="bat"></div>
    <div>qux: <input type="text" ng-model="qux"></div>
</div>

Oto, co się dzieje w kolejnych polach:

  • foo - początkowa wartość jest odziedziczona przez potomny scope, ale jest to tylko kopia modelu, więc de facto mamy dwa modele na dwóch różnych scopach, współdzielące nazwę, ale bez żadnego powiązania między sobą.
  • bar.baz - tu sprawa ma się inaczej, bo nie odwołujemy się do prostej wartości, a do właściwości obiektu. Oba scopy używają właściwości tego samego obiektu, więc istnieje dwustronne powiązanie (przypuszczam, że ma to związek z faktem, że zmienne z rodzica są przekazywane do potomnego scope w podobny sposób, jak argumenty funkcji — a więc obiekt jest referencją, podczas gdy zwykły string jest przekazywany jako wartość.
  • bat - na obu scopach niezależnie zadeklarowaliśmy dwie różne zmienne, przypadkowo o tej samej nazwie, ale niemające ze sobą nic wspólnego. Brak obustronnego powiązania.
  • qux - w scope potomnym wyraźnie zaznaczyliśmy, że model ma istnieć na rodzicu, zatem odwołanie do zmiennej z poziomu rodzica daje taki sam efekt, jak w przypadku bar.baz. Mamy zatem powiązanie obustronne.

Mam nadzieję, że ten przykład pomógł komuś zrozumieć, jak scope dziedziczy po swoim rodzicu i jak zadbać o to, żeby powiązania istniały wtedy, kiedy są potrzebne.


Komentarze

fas

2015-01-05, 18:15

mozna lepiej

Dominik Marczuk

2015-01-05, 18:18

Szczegóły proszę :)


Napisz komentarz


Szukaj wpisów


Chmura tagów