AngularJS: Synchronizácia dát v scope rodiča a potomka

V 4Q používame na implementáciu klienta už niekoľko rokov framework AngularJS. Za ten čas sa sme si položili mnoho otázok, na ktoré sme museli hľadať odpovede. Namiesto hľadania na stackoverflow, googli a skúšania možných riešení sme si vytvorili zoznam tipov a trikov. Seriál, ktorého prvú časť práve čítate, vám ich postupne priblíži.

1. Výmená dát medzi scope-ami

Jeden z častých problémov, s ktorými sa môžete stretnúť vychádza z nepochopenia ako funguje direktíva ng-model a toho, že ng-model je validný vo svojom scope a každá direktíva môže mať svoj vlastný scope ako napríklad ng-repeate. Môžeme si to vyskúšať na tomto príklade: http://jsfiddle.net/hv34kejf/

<div ng-app>
    <input type=”text” ng-model=”data”>
    <div ng-repeat=”i in [1]”>
        <input type=”text” ng-model=”data”><br/>
            inner scope:{{data}}
    </div>
    outer scope:{{data}}
</div>

Ak editujeme ako prvé input pole potomka, obsah sa zmení aj v rodičovskom. Avšak ak upravíme rodičovské input pole, toto prepojenie sa zruší a každé si „žije“ svojím životom. (hodnoty viac nie sú synchronizované). Vysvetlenie môžeme nájsť v dedičnosti objektov v javascripte. To vyzerá nasledovne:

ajs1

Keď zmeníme jeden z atribútov v objekte potomka, zmena sa v tomto objekte aj automaticky aplikuje:

ajs2

Avšak ak zmeníme hodnotu atribútu v rodičovskom objekte, nebude to viesť k vytvoreniu kópie hodnoty v objekte potomka a obe budú rovnaké:

ajs3

Celú túto dedičnosť môžeme vztiahnuť na scope v AngularJS. Direktívy ng-repeat, ng-include, ng-switch, ng-view, ng-controller a direktívy vytvorené s premennou scope: true, alebo transclude: true, vytvárajú oddedený scope. Direktívy, ktoré si svoj scope nevytvoria, alebo ho nezdedia, tento problém nepoznajú (ako napr. ng-if). Na riešenie problému môžeme použiť niekoľko spôsobov. Prvým, ktorý sme už vlastne spomenuli je zabaliť naše premenné do objektu. Ďalším je vytvoriť si kontrolný servis (viď tento príklad)

Ďalší spôsob ako riešiť tento problém je použiť kontroler ako „data wrapper“. Namiesto objektu scope teda môžeme pre ukladanie dát použiť samotný kontroler (angularjs controller). Najlepšie si to ukážeme na tomto príklade:

<html>
<head>
    <script src=”angular.min.js”></script>
</head>
<body>
    <h3>Custom transclude demo</h3>
    <div ng-app=”demo3″ ng-controller=”DemoCtrl as ctrl”>
        <input type=”text” ng-model=”ctrl.premenna”>
        <div ng-repeat=”i in [1]”>
            <input type=”text” ng-model=”ctrl.premenna”><br/>
            scope potomka: {{ctrl.premenna}}
        </div>
        rodičovský scope: {{ctrl.premenna}}
        <div>premenná kontrolera: {{ctrl.premenna}} </div>
    </div>
</body>
</html>
<script>
    var app = angular.module(‘demo3’, []);
    app.controller(‘DemoCtrl’, function($scope) {
        this.premenna=”prem. ctrl”
    });
</script>

Údaje máme uložené v kontroleri a preto nezáleží na tom, ktorý scope použije direktívy a naša premenná bude na oboch miestach vždy rovnaká. Toto riešenie však obchádza jeden zo základných princípov AngularJS a teda, že dáta sú uložené v scope a aplikuje sa na nich takzvaný „two way data-binding“.