Spec-Zone.ru › Angular.js 1.7

Улучшить эту документациюКомпоненты

В AngularJS, компонент — это особый вид директивы, использующий более простую конфигурацию, подходящую для архитектуры приложения, основанной на компонентах.

Это упрощает написание приложения, аналогично использованию Web Components или новой архитектуры приложений Angular.

Преимущества компонентов:

  • более простая конфигурация по сравнению с обычными директивами
  • поощряют разумные значения по умолчанию и лучшие практики
  • оптимизированы для архитектуры, основанной на компонентах
  • написание директивы компонента упростит обновление до Angular

Когда не следует использовать компоненты:

  • для директив, которые должны выполнять действия в функциях компиляции и предварительной привязки, так как они недоступны
  • когда вам нужны расширенные параметры определения директивы, такие как приоритет, терминал, многоэлементная директива
  • когда вам нужна директива, срабатывающая по атрибуту или классу CSS, а не элементу

Создание и настройка компонента

Компоненты можно зарегистрировать с помощью метода .component() модуля AngularJS (возвращаемого методом angular.module()). Метод принимает два аргумента:

  • Имя компонента (в виде строки).
  • Объект конфигурации компонента. (Обратите внимание, что, в отличие от метода .directive() , этот метод не принимает функцию фабрики.)

Также можно добавить компоненты с помощью метода $compileProvider на стадии конфигурации модуля.

Сравнение определения директивы и определения компонента

Директива Компонент
bindings Нет Да (привязки к контроллеру)
bindToController Да (по умолчанию: false) Нет (используйте bindings вместо этого)
compile function Да Нет
controller Да Да (по умолчанию function() {})
controllerAs Да (по умолчанию: false) Да (по умолчанию: $ctrl)
link functions Да Нет
multiElement Да Нет
priority Да Нет
replace Да (устарело) Нет
require Да Да
restrict Да Нет (ограничено только элементами)
scope Да (по умолчанию: false) Нет (объем всегда изолированный)
template Да Да, инжектируемый
templateNamespace Да Нет
templateUrl Да Да, инжектируемый
terminal Да Нет
transclude Да (по умолчанию: false) Да (по умолчанию: false)

Архитектура приложения, основанная на компонентах

Как уже упоминалось, помощник по компонентам упрощает структурирование приложения с помощью архитектуры, основанной на компонентах. Но что отличает компонент помимо параметров, предоставляемых помощником по компонентам?

  • Компоненты контролируют только свой собственный вид и данные: Компоненты никогда не должны изменять данные или DOM, выходящие за рамки своего собственного объема. Обычно в AngularJS можно изменить данные в любом месте приложения через наследование области видимости и наблюдения. Это удобно, но также может привести к проблемам, когда не ясно, какая часть приложения отвечает за изменение данных. Именно поэтому директивы компонентов используют изолированный объем видимости, чтобы целая категория манипуляций с объемом видимости не была возможна.

  • Компоненты имеют четко определенный публичный API — вводы и выводы: Однако изоляция объема видимости простирается не так далеко, потому что AngularJS использует двунаправленную привязку. Поэтому, если вы передаете объект компоненту таким образом — bindings: {item: '='}, и изменяете одно из его свойств, изменение отразится в родительском компоненте. Однако для компонентов только компонент, владеющий данными, должен их изменять, чтобы упростить определение того, какие данные меняются и когда. По этой причине компоненты должны следовать нескольким простым соглашениям:

    • Вводы должны использовать < и @ привязки. Символ < обозначает односторонние привязки, доступные начиная с версии 1.5. Разница с = заключается в том, что связанные свойства в объеме видимости компонента не отслеживаются, что означает, что если вы присваиваете новое значение свойству в объеме видимости компонента, родительский объем видимости не будет обновлен. Тем не менее, и родительский, и компонентный объем видимости ссылаются на один и тот же объект, поэтому если вы изменяете свойства объекта или элементы массива в объеме видимости компонента, родительский объем видимости все равно будет отражать это изменение. Поэтому общее правило должно заключаться в том, чтобы никогда не изменять свойства объекта или массива в объеме видимости компонента. @ привязки могут использоваться, когда вход — это строка, особенно когда значение привязки не изменяется.
      bindings: {
        hero: '<',
        comment: '@'
      }
      
    • Выходы реализуются с помощью & привязок, которые работают как обратные вызовы событий компонента.
      bindings: {
        onDelete: '&',
        onUpdate: '&'
      }
      
    • Вместо манипулирования входными данными компонент вызывает правильное событие вывода с измененными данными. Для удаления это означает, что компонент не удаляет hero сам по себе, а отправляет его обратно родительскому компоненту через соответствующее событие.
      <!-- note that we use kebab-case for bindings in the template as usual -->
      <editable-field on-update="$ctrl.update('location', value)"></editable-field><br>
      <button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
      
    • Таким образом, родительский компонент может решить, что делать с событием (например, удалить элемент или обновить свойства)
      ctrl.deleteHero(hero) {
        $http.delete(...).then(function() {
          var idx = ctrl.list.indexOf(hero);
          if (idx >= 0) {
            ctrl.list.splice(idx, 1);
          }
        });
      }
      
  • Компоненты имеют четко определенный жизненный цикл: Каждый компонент может реализовать «методы жизненного цикла». Это методы, которые будут вызываться в определенные моменты жизни компонента. Следующие методы жизненного цикла могут быть реализованы:

    • $onInit() — вызывается для каждого контроллера после того, как все контроллеры элемента были созданы и их привязки были инициализированы (и до функций предварительной и последующей привязки для директив этого элемента). Это хорошее место для размещения кода инициализации вашего контроллера.
    • $onChanges(changesObj) — вызывается всякий раз, когда односторонние привязки обновляются. changesObj — это хеш, ключи которого — имена измененных связанных свойств, а значения — объект формы { currentValue, previousValue, isFirstChange() }. Используйте этот метод для запуска обновлений внутри компонента, например, для клонирования связанного значения, чтобы предотвратить случайное изменение внешнего значения.
    • $doCheck() — вызывается на каждом шаге цикла переваривания. Предоставляет возможность обнаруживать и реагировать на изменения. Любые действия, которые вы хотите предпринять в ответ на обнаруженные изменения, должны вызываться из этого метода; реализация этого не влияет на то, когда $onChanges вызывается. Например, этот метод может быть полезен, если вы хотите выполнить проверку глубокого равенства или проверить объект Date, изменения в котором не будут обнаружены детектором изменений AngularJS и, таким образом, не вызовут $onChanges. Этот метод вызывается без аргументов; если вы обнаруживаете изменения, вы должны сохранить предыдущее(ые) значение(я) для сравнения с текущими значениями.
    • $onDestroy() — вызывается для контроллера, когда содержащий его объем видимости уничтожается. Используйте этот метод для освобождения внешних ресурсов, наблюдений и обработчиков событий.
    • $postLink() — вызывается после того, как элемент этого контроллера и его дочерние элементы были связаны. Подобно функции пост-привязки, этот метод можно использовать для настройки обработчиков событий DOM и выполнения непосредственных манипуляций с DOM. Обратите внимание, что дочерние элементы, содержащие templateUrl директивы, не будут скомпилированы и связаны, поскольку они ожидают загрузки их шаблона асинхронно, и их собственная компиляция и привязка приостановлены до этого момента. Этот метод можно считать аналогичным методам ngAfterViewInit и ngAfterContentInit в Angular. Поскольку процесс компиляции в AngularJS несколько отличается, нет прямого соответствия, и следует проявлять осторожность при обновлении.

Реализуя эти методы, ваш компонент может подключаться к своему жизненному циклу.

  • Приложение — это дерево компонентов: В идеале все приложение должно быть деревом компонентов, которые реализуют четко определенные вводы и выводы и минимизируют двунаправленную привязку данных. Таким образом, проще предсказать, когда данные изменяются и какое состояние имеет компонент.

Пример древовидной структуры компонента

Следующий пример расширяет простой пример компонента и включает в себя вышеупомянутые понятия:

Вместо ngController у нас теперь есть компонент heroList, который содержит данные разных героев и создает heroDetail для каждого из них.

Компонент heroDetail теперь содержит новые функции:

  • кнопка удаления, которая вызывает связанную onDelete функцию компонента heroList
  • поле ввода для изменения местоположения героя в виде повторно используемого компонента editableField. Вместо манипулирования объектом hero сам по себе он отправляет набор изменений вверх в heroDetail, который отправляет его вверх в компонент heroList, который обновляет исходные данные.

Компоненты как шаблоны маршрутов

Компоненты также полезны в качестве шаблонов маршрутов (например, при использовании ngRoute). В приложении, основанном на компонентах, каждый вид — это компонент:

var myMod = angular.module('myMod', ['ngRoute']);
myMod.component('home', {
  template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
  controller: function() {
    this.user = {name: 'world'};
  }
});
myMod.config(function($routeProvider) {
  $routeProvider.when('/', {
    template: '<home></home>'
  });
});


При использовании $routeProvider, вы часто можете избежать избыточного кода, передавая разрешенные зависимости маршрута непосредственно компоненту. Начиная с версии 1.5, ngRoute автоматически назначает разрешения свойству маршрута $resolve (вы также можете настроить имя свойства с помощью resolveAs). Используя компоненты, вы можете воспользоваться этим и передать разрешения непосредственно в свой компонент, не создавая дополнительный контроллер маршрута:

var myMod = angular.module('myMod', ['ngRoute']);
myMod.component('home', {
  template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
  bindings: {
    user: '<'
  }
});
myMod.config(function($routeProvider) {
  $routeProvider.when('/', {
    template: '<home user="$resolve.user"></home>',
    resolve: {
      user: function($http) { return $http.get('...'); }
    }
  });
});

Взаимодействие компонентов

Директивы могут требовать контроллеры других директив, чтобы обеспечить взаимодействие между ними. Это можно сделать в компоненте, предоставив объектную карту для свойства require. Ключи объекта определяют имена свойств, по которым требуемые контроллеры (значения объекта) будут привязаны к контроллеру запрашивающего компонента.

Обратите внимание, что требуемые контроллеры не будут доступны во время создания контроллера, но они гарантированно станут доступны непосредственно перед выполнением метода $onInit!

Вот пример раздела вкладки, созданный из компонентов:

Тестирование компонентов контроллеров

Самый простой способ провести модульное тестирование контроллера компонента — использовать $componentController, который включён в ngMock. Преимущество этого метода в том, что вам не нужно создавать какие-либо DOM-элементы. Следующий пример демонстрирует, как это сделать для компонента heroDetail из вышеприведённого примера.

В примерах используется фреймворк для тестирования Jasmine.

Тест контроллера:

describe('HeroDetailController', function() {
  var $componentController;

  beforeEach(module('heroApp'));
  beforeEach(inject(function(_$componentController_) {
    $componentController = _$componentController_;
  }));

  it('should call the `onDelete` binding, when deleting the hero', function() {
    var onDeleteSpy = jasmine.createSpy('onDelete');
    var bindings = {hero: {}, onDelete: onDeleteSpy};
    var ctrl = $componentController('heroDetail', null, bindings);

    ctrl.delete();
    expect(onDeleteSpy).toHaveBeenCalledWith({hero: ctrl.hero});
  });

  it('should call the `onUpdate` binding, when updating a property', function() {
    var onUpdateSpy = jasmine.createSpy('onUpdate');
    var bindings = {hero: {}, onUpdate: onUpdateSpy};
    var ctrl = $componentController('heroDetail', null, bindings);

    ctrl.update('foo', 'bar');
    expect(onUpdateSpy).toHaveBeenCalledWith({
      hero: ctrl.hero,
      prop: 'foo',
      value: 'bar'
    });
  });

});

© 2010–2018 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.7.8/docs/guide/component

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API