Инструменты пользователя

Инструменты сайта


angular:angularjs:custom_directive:component

Это старая версия документа!


от directive() к component()

В 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 и BindToController становятся просто Bindings

В директиве мы можем задавать scope 3-мя способами: родительский(скоуп не создается), наследник от родительского, изолированный. Со временем мы приходим к выводу, что изолированный скоуп, где мы четко задаем входящие параметры, наилучший вариант. Так же каждый раз для изолированного скоупа нам приходиться прописывать bindToController, чтобы прокинуть данные со скоупа непосредственно на контроллер директивы.

Свойство компонента bindings позволяет использовать 2 в одном, так как компонент использует изолированный скоуп по умолчанию:

// before
.directive('counter', function counter() {
  return {
    scope: {},
    bindToController: {
      count: '='
    }
  };
});
 
// after
.component('counter', {
  bindings: {
    count: '='
  }
});

Controller и ControllerAs

Ничего не изменилось в способе задания контроллера, однако теперь 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 '...';
  }
  ...
}

Улучшенное require

Да, это свершилось! Теперь мы можем задать имя для контроллера, подключаемого к нашему компоненту, и обратиться к нему из контроллера( до этого только из метода 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().

Обновляемся до Angular2

Как уже было сказано: использование метода .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 = 0;
  },
  increment: function () {
    this.count++;
  },
  decrement: function () {
    this.count--;
  }
});
angular/angularjs/custom_directive/component.1626121777.txt.gz · Последние изменения: 2023/01/12 12:15 (внешнее изменение)