Оглавление:
Карта сайта:
Оглавление:
Карта сайта:
Это старая версия документа!
В Angular 1.5 нам был представлен новый метод .component(), который намного проще чем .directive() и при этом он использует все лучшее по умолчанию. Метод .component() также позволит разработчикам писать в Angular2 стиле, то есть сделает переход на вторую версию максимально безболезненным.
В этом посте мы попробуем параллельно разобрать старый и новый подходы для создания компонентов.
На примере простой директивы counter мы посмотрим как можно создать компонент с аналогичной функциональностью:
<!DOCTYPE html> <html ng-app="app"> <head> <title></title> <script src="http://crm.localhost/vendor/angular/angular.min.js"></script> <link href="../bootstrap-theme.css" rel="stylesheet" /> <link href="../bootstrap.css" rel="stylesheet" /> <script> angular.module("app", []) .controller("studyCtrl", function ($scope) { $scope.count = 5; }) .directive('counter', function counter() { return { scope: {}, bindToController: { count: '=' }, controller: function () { function increment() { this.count++; } function decrement() { this.count--; } this.increment = increment; this.decrement = decrement; }, controllerAs: 'counter', template: [ '<div class="form-group">', '<label class="control-label col-md-1" for="">Директива</label>', '<div class="input-group col-md-2">', '<div class="input-group-addon" ng-click="counter.decrement();">-</div>', '<input class="form-control" type="text" ng-model="counter.count">', '<div class="input-group-addon" ng-click="counter.increment();">+</div>', '</div>', '</div>' ].join('') }; }); </script> </head> <body ng-controller="studyCtrl as vm"> <div class="container"> <br> <div class="row"> <div class="col-md-1"><b>контроллер</b></div> <div class="col-md-2">{{count}}</div> </div> <br> <div class="row"> <label><counter count="count"></counter></label> </div> </div> </body> </html>
Начнем наш анализ со способа задания и обратим внимание на то, что параметры в компонент передаются как объект (а не функция, что было в директиве):
// до .directive('counter', function counter() { return { }; }); // после .component('counter', { });
В директиве мы можем задавать scope 3-мя способами: родительский(скоуп не создается), наследник от родительского, изолированный. Со временем мы приходим к выводу, что изолированный скоуп, где мы четко задаем входящие параметры, наилучший вариант. Так же каждый раз для изолированного скоупа нам приходиться прописывать bindToController, чтобы прокинуть данные со скоупа непосредственно на контроллер директивы.
Свойство компонента bindings позволяет использовать 2 в одном, так как компонент использует изолированный скоуп по умолчанию:
// before .directive('counter', function counter() { return { scope: {}, bindToController: { count: '=' } }; }); // after .component('counter', { bindings: { count: '=' } });
Ничего не изменилось в способе задания контроллера, однако теперь controllerAs параметр по умолчанию, который задан как “$ctrl“: то есть если мы в контроллере напишем:
this.x = 5;
то в шаблоне компонента потом можно будет обратиться вот так:
<div>{{$ctrl.x}}</div>
Итак, что у нас получилось с контроллером для обоих случаев:
до
<!DOCTYPE html> <html ng-app="app"> <head> <title></title> <script src="http://crm.localhost/vendor/angular/angular.min.js"></script> <link href="../bootstrap-theme.css" rel="stylesheet" /> <link href="../bootstrap.css" rel="stylesheet" /> <script> angular.module("app", []) .controller("studyCtrl", function ($scope) { $scope.count = 5; }) .directive('counter', function counter() { return { scope: {}, bindToController: { count: '=' }, controller: function () { function increment() { this.count++; } function decrement() { this.count--; } this.increment = increment; this.decrement = decrement; }, controllerAs: 'counter', template: [ '<div class="form-group">', '<label class="control-label col-md-1" for="">Директива</label>', '<div class="input-group col-md-2">', '<div class="input-group-addon" ng-click="counter.decrement();">-</div>', '<input class="form-control" type="text" ng-model="counter.count">', '<div class="input-group-addon" ng-click="counter.increment();">+</div>', '</div>', '</div>' ].join('') }; }); </script> </head> <body ng-controller="studyCtrl as vm"> <div class="container"> <br> <div class="row"> <div class="col-md-1"><b>контроллер</b></div> <div class="col-md-2">{{count}}</div> </div> <br> <div class="row"> <label><counter count="count"></counter></label> </div> </div> </body> </html>
после
<!DOCTYPE html> <html ng-app="app"> <head> <title></title> <script src="http://crm.localhost/vendor/angular/angular.min.js"></script> <link href="../bootstrap-theme.css" rel="stylesheet" /> <link href="../bootstrap.css" rel="stylesheet" /> <script> angular.module("app", []) .controller("studyCtrl", function ($scope) { $scope.count = 5; }) .component('counter', { bindings: { count: '=' }, controller: function () { function increment() { this.count++; } function decrement() { this.count--; } this.increment = increment; this.decrement = decrement; }, template: function($element, $attrs){ return [ '<div class="form-group">', '<label class="control-label col-md-1" for="">Директива</label>', '<div class="input-group col-md-2">', '<div class="input-group-addon" ng-click="$ctrl.decrement()">-</div>', '<input class="form-control" type="text" ng-model="$ctrl.count">', '<div class="input-group-addon" ng-click="$ctrl.increment()">+</div>', '</div>', '</div>' ].join('') } }); </script> </head> <body ng-controller="studyCtrl as vm"> <div class="container"> <br> <div class="row"> <div class="col-md-1"><b>контроллер</b></div> <div class="col-md-2">{{count}}</div> </div> <br> <div class="row"> <label><counter count="count"></counter></label> </div> </div> </body> </html>
Я очень упростил для понимания пункт из статьи, поэтому рекомендую также заглянуть в оригинал.
В определении шаблонов есть небольшое различие: шаблон компонента может задаваться как функция, в которую инжектятся элемент и атрибуты:
{ ... template: function ($element, $attrs) { // access to $element and $attrs return '...'; } ... }
Да, это свершилось! Теперь мы можем задать имя для контроллера, подключаемого к нашему компоненту, и обратиться к нему из контроллера( до этого только из метода link, а в контроллер оно попадало только путем ужасных костылей):
{ ... require: { parent: '^parentComponent' }, controller: function () { // use this.parent to access required Objects this.parent.foo(); } ... }
В данном случае мы определили подключаемый контроллер на свойстве parent.
Еще одна фишка компонентов и Angular1.5 это одностороннее связывание, которое определяется следующим синтаксисом:
{ ... bindings: { oneWay: '<', twoWay: '=' }, ... }
если мы задали свойство oneWay таким образом, то оно будет реагировать на изменения внешнего связанного объекта, при этом свои изменения передавать “наружу” не будет. И да, сразу отвечу на вопрос, который у вас наверное появился: работает только в одну сторону.
Если вы посмотрите на исходный код, то увидите что разработчики AngularJS особо не парились и сделали метод component() просто оболочкой над directive().
Как уже было сказано: использование метода .component() серьезно упростит переход на Angular2. Посмотрите как будет выглядеть ваш компонент во второй версии фреймворка(конечно, с новым синтаксисом шаблонов):
var Counter = ng .Component({ selector: 'counter', template: [ '<div class="form-group">', '<label class="control-label col-md-1" for="">Директива</label>', '<div class="input-group col-md-2">', '<div class="input-group-addon" ng-click="$ctrl.decrement()">-</div>', '<input class="form-control" type="text" ng-model="$ctrl.count">', '<div class="input-group-addon" ng-click="$ctrl.increment()">+</div>', '</div>', '</div>' ].join('') }) .Class({ constructor: function () { this.count = 5; }, increment: function () { this.count++; }, decrement: function () { this.count--; } });