Angular-styleguide: Отсутствует руководство по стилю для компонента

Созданный на 1 сент. 2016  ·  25Комментарии  ·  Источник: johnpapa/angular-styleguide

Вместо использования директив мы можем использовать компонент в тех местах, где нам не нужно манипулировать DOM. Но я не могу найти руководство по стилю для этого в angularjs 1.5.

Angular 1 enhancement help wanted

Самый полезный комментарий

Лично я использую следующий стиль:

(function(){
  'use strict';

  angular
    .module('app')
    .component('exampleComponent', exampleComponent());

  function exampleComponent() {
    var component = {
      bindings: {
        /* bindings - avoiding "=" */
      },
      controller: ExampleComponentController,
      controllerAs: 'vm',
      templateUrl: 'path/to/example-component.html'
    };

    return component;
  }

  ExampleComponentController.$inject = ['exampleService'];

  /* <strong i="6">@ngInject</strong> */
  function ExampleComponentController(exampleService) {
    var vm = this;

    /* generic controller code */
  }
})();

Все 25 Комментарий

Какие предложения есть у сообщества по этому поводу?

Я абсолютно хочу раздел компонентов

Мне так нравится:

(function() {
    'use strict';

    angular
        .module('app.widget.activeplaces')
        .component('activePlaces', {
            bindings: {
                data: '<'
            },
            templateUrl: 'components/active-places/active-places.widget.html',
            controller: ActivePlacesController,
            controllerAs: 'vm'
        });

    function ActivePlacesController(activityService) {
        var vm = this;

        vm.activities = [];
        vm.doFunnyStuff = doFunnyStuff;

        function doFunnyStuff() {
            vm.activities.push('yolo');
        }
    }

}());

В качестве альтернативы переместите регистрацию компонентов вниз:

(function() {
    'use strict';

    var activePlaces = {
        bindings: {
            data: '<'
        },
        templateUrl: 'components/active-places/active-places.widget.html',
        controller: ActivePlacesController,
        controllerAs: 'vm'
    };

    function ActivePlacesController(activityService) {
        var vm = this;

        vm.activities = [];
        vm.doFunnyStuff = doFunnyStuff;

        function doFunnyStuff() {
            vm.activities.push('yolo');
        }
    }

    angular
        .module('app.widget.activeplaces')
        .component('activePlaces', activePlaces);

}());

Проблема в том, что я предпочел бы использовать новую ссылку $ ctrl вместо vm, что делает vm-шаблон несовместимым. Так что либо полностью переключитесь на $ ctrl, либо просто добавьте controllerAs: 'vm' в объект настроек компонента, чтобы он соответствовал остальной части руководства по стилю :)

Мне больше нравится второй способ. Я бы также подумал о применении методов в прототипе, чтобы облегчить переход на классы ES6 позже. Я также согласен, что пора переключиться на $ctrl поскольку это значение по умолчанию, и отсутствие необходимости объявлять controllerAs уменьшает необходимый шаблон.

Я предпочитаю первый способ fwiw: grin:
(без vm курса: stuck_out_tongue :)

Первый метод - мой личный фаворит. Я бы добавил к обоим контроллерам:

// other vm vars/assigns here
vm.$onInit = activate;

// functions
function activate() {
  // initialization magic here
}

конечно, мы можем изменить имя activate function на init или onInit и т. д.

Это немного более самоуверенный и ES2016, но для моих компонентов я делаю это так:

(function() {
  'use strict';
  /*
<active-places data="">
</active-places>
   */
  const componentConfig = {
    bindings: { data: '<' },
    templateUrl: 'components/active-places/active-places.widget.html',
    controller: ActivePlacesController,
  };

  /* <strong i="6">@ngInject</strong> */
  function ActivePlacesController(activityService) {
    Object.assign(this, {
      activities: [],
      doFunnyStuff, 
      $onInit,
    })

    function $onInit() {
      activityService.list()
        .then(response => { this.activities = response })
        .catch(console.error)
    }
    function doFunnyStuff() {
      this.activities.push('yolo');
    }
  }
  angular
    .module('app.widget.activeplaces')
    .component('activePlaces', componentConfig);

}());

Мне нравится иметь копируемый фрагмент вверху моего файла, что позволяет мне сэкономить много времени. И это более явный и полезный при разработке в проекте, чем объявление модуля и компонента AMHO.
Мне также нравится вызов Object.assign (ES2015 может использовать angular.extend ) для привязки всех методов и свойств к области компонента, а также для удаления всех этих маленьких vm. из файла.
По возможности я использую сокращенные обозначения объектов ES6.
Я также удалил переменную захвата vm для использования this . Начиная с ES2015, он стал более удобным в использовании, а с NG1.5 + я всегда нахожу способ не использовать $ watch. Бонусный эффект, я учусь им пользоваться, а не убегать от него.

Вот версия с использованием ES6, если вам интересно.

https://github.com/rwwagner90/angular-styleguide-es6

Это тоже самоуверенно, но я предпочитаю, чтобы это было просто. Нет vm, просто $ ctrl. Нет разделения на то, что будет выставлено на обозрение. Мысли?

    (function(){
        'use strict';

        angular
            .module('app.shared')
            .component('buttonList', {
                bindings: {
                    data: '<',
                },
                template: `<span ng-repeat="button in $ctrl.buttons">
                                <button class="btn btn-default" ng-click="$ctrl.removeButton(button)">{{ ::button.name }}</button>
                            </span>`,
                controller: MyDataController
            });

        function MyDataController(dataService) {

            this.$onInit = function () {
                this.buttons = [];
                this.loadData();
            };

            this.removeButton = function (button) {
                var index = this.buttons.indexOf(button);
                this.buttons.splice(index, 1);
            };

        }

    })();

Я помог команде создать функцию .component для A1 ... и я очень рад, что они это сделали.

Я также публично проголосовал за то, чтобы vm.* продолжил работу над версией A1 .component . Но сообщество проголосовало за то, чтобы оставить $ctrl . В духе руководства по стилю наиболее важна последовательность. Таким образом, я думаю, что $ctrl - лучший вариант здесь, и он соответствует значениям по умолчанию, упомянутым в ваших примечаниях выше.

Другие вещи, которые следует учитывать,

  1. как определить конфигурацию
  2. как установить свойства в компоненте ( this , vm , $ctrl )
    2b. при использовании this (см. # 2 выше) как обрабатывать новые закрытия (например, self) ... yuk
  3. Название функции (компонент или контроллер)
  4. как обрабатывать обратные вызовы в привязках
  5. наименование привязок

Это лишь некоторые примеры, которые я извлек из множества способов, которые я видел и использовал .component сих пор.

Давайте продолжим беседу по этим и другим актуальным вопросам.

Мне нравится 1-й формат, который объявляет компонент наверху. Причина в том, что вторая версия вводит новую переменную activePlaces , имя которой необходимо обсудить и которая добавляет сложности чтению файла.

Я также хотел бы поместить методы контроллера в прототип. В отличие от сервисов, вы можете получить несколько экземпляров контроллеров, особенно если они используются в компонентах, и было бы лучше не создавать новые копии каждого метода для каждого экземпляра.

Поскольку для компонентов требуется контроллер, я предпочитаю значение по умолчанию $ctrl поэтому вы, как правило, не так часто получаете вызовы бесконтекстных функций для методов контроллера.

Я всегда вызывал функцию [ComponentName]Controller поскольку это контроллер компонента. Я бы порекомендовал это, несмотря на то, что при переходе на A2 эти имена изменятся - это будет простой рефакторинг.

В компонентах важно использовать хуки жизненного цикла, а не конструктор контроллера:

  • constructor - только простая инициализация свойств, например, присоединение внедренных сервисов к свойствам. Здесь не следует выполнять никакой реальной работы, поскольку в дальнейшем никакие привязки здесь не будут назначаться.
  • $onInit - на этом этапе привязки и все необходимые контроллеры назначены. Именно здесь следует выполнять любую реальную работу по инициализации.
  • $onChanges - это вызывается всякий раз, когда меняются привязки ввода ( < ) и интерполяции ( @ ). Важно понимать, что изменение входных объектов не вызывает интерполяцию. Но это хорошее место для размещения кода, который реагирует на изменения входных данных, что позволяет сэкономить на создании часов. Я бы посоветовал, если вы выбираете подход «одностороннего потока данных», то это хорошее место для создания копии входных объектов по мере их изменения, чтобы вы случайно не изменили переданный объект.
  • $onDestroy - единственная причина для этого метода - освободить или закрыть долго работающие ресурсы, которые были получены или открыты в течение жизненного цикла компонента. Обратите внимание, что эта ловушка не вызывается, если вы вручную уничтожаете область изолированного компонента или если вы вручную удаляете элемент компонента из DOM. Он уничтожается только в случае уничтожения содержащей его области видимости, в которой находится этот компонент.

Для привязок вывода я использовал метод обработчика buttonClick(button) который фактически запускает вывод onButtonClick({$event: button}) . И что полезная нагрузка для вывода всегда помещается в свойство $event . Это означает, что снаружи вы можете делать <button-list on-button-click="$ctrl.doStuff($event)"> что похоже на то, что вы видите в Angular 2.

Все это выглядит так:

    (function(){
        'use strict';

        angular
            .module('app.shared')
            .component('buttonList', {
                bindings: {
                    buttons: '<',
                    onButtonClick: '&'
                },
                template: `<span ng-repeat="button in $ctrl.buttons">
                                <button class="btn btn-default" ng-click="$ctrl.buttonClick(button)">{{ ::button.name }}</button>
                            </span>`,
                controller: ButtonListController
            });

        function ButtonListController(dataService) {
            this.dataService = dataService;
        }

        ButtonListController.prototype = {
            $onInit: function () {
                this.dataService.loadData().then(function(data) {
                  this.data = data;
                }.bind(this));
            },
            $onChange: function(changes) {
                if (changes.buttons) {
                    this.buttons = angular.copy(this.buttons);
                }
            },
            buttonClick: function (button) {
              this.onButtonClick({ $event: { button });
            }
        };
    })();

@petebacondarwin Мне нравится то, что у вас есть, с некоторыми изменениями.

Я предпочитаю не встраивать { } для конфигурации компонента и вместо этого использовать переменную. Мне легче читать и отлаживать. Имя мне не имеет отношения ...

var componentConfig = { ... };
angular
            .module('app.shared')
            .component('buttonList', componentConfig);

Больше всего меня беспокоит использование this и то, как функции могут получить доступ к свойствам контроллера. Контроллер будет иметь состояние ... у вас $ctrl.buttons в шаблоне, и я предполагаю, что у нас будет this.buttons в контроллере. Когда мы создаем функции (прототипы или другие), у нас есть замыкания, и у некоторых из них есть собственная ссылка на this . Это происходит, например, когда мы используем $timeout() . Как вы видите их решение?

У меня была эта проблема в моем примере с обработчиком обещаний. Я решил использовать .bind

да, но это не весело :)

это еще одна причина, по которой мы использовали vm в контроллерах A1

Что ж, в этом случае вы всегда можете использовать $ ctrl

14 сентября 2016 г., 19:05, «Джон Папа» [email protected] написал:

да, но это не весело :)

это еще одна причина, по которой мы использовали vm в контроллерах A1

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/johnpapa/angular-styleguide/issues/766#issuecomment -247103228,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AAA9J5DE4qIUEOEuPEIPLKobfDKT_Bbjks5qqDeEgaJpZM4Jyo-5
.

Прямо сейчас я делаю это так (иду с $ ctrl):

( function() {
  'use strict';

  var component = {
    bindings: {
      data: '='
    },
    controller: controller,
    templateUrl: '/breadcrumbs.html'
  };

  angular.module( 'app' ).component( 'breadcrumbs', component );


  function controller( $log, $state ) {
    var logger = $log.getLogger( 'breadcrumbs' );
    var $ctrl = this;

    logger.log( $ctrl );

    $ctrl.goToState = goToState;


    function goToState( breadcrumb ) {
      logger.log( 'goToState()', breadcrumb );
      $state.go( breadcrumb.state, breadcrumb.data );
    }
  }
} )();

Что вы ребята думаете?

Думаю, можно было вдохновиться руководством по стилю Todd Motto?
https://github.com/toddmotto/angular-styleguide

Да, но это ES2015. Что происходит с людьми, которые все еще придерживаются версии до ES2015?

Лично я использую следующий стиль:

(function(){
  'use strict';

  angular
    .module('app')
    .component('exampleComponent', exampleComponent());

  function exampleComponent() {
    var component = {
      bindings: {
        /* bindings - avoiding "=" */
      },
      controller: ExampleComponentController,
      controllerAs: 'vm',
      templateUrl: 'path/to/example-component.html'
    };

    return component;
  }

  ExampleComponentController.$inject = ['exampleService'];

  /* <strong i="6">@ngInject</strong> */
  function ExampleComponentController(exampleService) {
    var vm = this;

    /* generic controller code */
  }
})();

Как насчет предоставления уникального префикса для компонента, как в директиве? Считается ли это необходимым?

@petebacondarwin Мне нравится ваш пример, поскольку он подчеркивает односторонние привязки данных и необходимость их копирования в ловушку жизненного цикла $onChange чтобы действительно получить односторонние данные (т.е. к родителю). Несколько вопросов:

  1. Почему вы вызвали this.dataService.loadData() внутри хука $onInit ? Насколько я понимаю, поскольку dataService объявлен как зависимость как параметр контроллера, механизмы внедрения зависимостей angular обеспечат доступность такой (одноэлементной) службы при создании объекта контроллера. Имеет ли смысл использовать часть require API компонента / директивы, чтобы вместо этого продемонстрировать гарантию $onInit ?
  2. Зачем вам добавлять к prototype из ButtonListController если вы можете так же легко добавить эти методы внутри самой функции конструктора без каких-либо потерь в поведении, особенно учитывая, что ButtonListController не будет использоваться как часть некоторой прототипической цепочки наследования. Вы что-то пытаетесь здесь подчеркнуть?

@texonidas :

  1. Вам нужно явно прикрепить " $inject ables" к функции-конструктору, которая ссылается на них как на параметры?
  2. Зачем вводить exampleComponent() если вы можете просто передать конфигурацию компонента (литерал объекта) непосредственно в качестве параметра методу component ? Среди прочего, вы вводите переменную с именем component , только чтобы вернуть ту же переменную и метод с тем же именем.

@jbmilgrom

  1. Гораздо проще контролировать свои модульные тесты, если вы не помещаете «настоящую» работу в конструктор. Если ваш конструктор действительно работает, например, при загрузке, то невозможно предотвратить это (или задержать его) внутри ваших тестов. С дополнительным усложнением системы DI, выполняющей создание экземпляра, вы теряете еще больше контроля над временем.

  2. Учитывая, что этот компонент может использоваться много раз в одном приложении, более эффективно с точки зрения памяти помещать в прототип методы, которые будут совместно использоваться экземплярами.

@petebacondarwin спасибо за ответ, имеет смысл.

Объявляя методы в области видимости функции конструктора, которые ссылаются на переменные, также объявленные в области видимости функции конструктора, вы можете получить какое-то частное состояние (через закрытие). Для контроллеров, которые могут делать слишком много, это может быть полезно, помимо прочего, для различения состояния, которое потенциально может быть изменено непосредственно в шаблоне, и состояния, которое, несомненно, не может. Кажется, это невозможно в вашей настройке. Вместо этого вы привязываете все состояние к this и делегируете их службам, если требуется конфиденциальность?

@jbmilgrom

  1. $ Inject нужен для безопасности минификации. Это не обязательно, и если бы это должно было войти в руководство по стилю, я бы удалил его, так как он подробно изложен далее в разделе, посвященном минификации.

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

/* recommended */
function dataService() {
    var someValue = '';
    var service = {
        save: save,
        someValue: someValue,
        validate: validate
    };
    return service;

    ////////////

    function save() {
        /* */
    };

    function validate() {
        /* */
    };
}

в том, что вы явно называете переменную, а затем возвращаете ее.

Я использую подход, похожий на метод Texonidas, но решил оставить значение по умолчанию $ ctrl в html (не используя controllerAs).

имя файла: example.component.js

    .module('app.moduleName')
    .component('exampleComponent', componentConfig());

function componentConfig () {
    return{
        bindings: {
            /*bindings*/
        },
        templateUrl: 'path/to/example.html',
        controller: exampleController
    };    
};

exampleController.$inject = ['exampleService'];

function exampleController (exampleService){
    var ctrl = this;
    ctrl.someFunction = someFunction;
    ctrl.$onInit = function(){
        ctrl.someVar = 'initial values';
   }
   .
   .
   .
};

Следует ли регистрировать компонент в модуле?

Моя команда интерпретирует руководство по стилю v1 так, что все контроллеры должны быть зарегистрированы в модуле и на них следует ссылаться по имени.

    .module('app.moduleName')
    .controller('exampleController', exampleController)
    .component('exampleComponent', componentConfig());


function componentConfig () {
    return{
        bindings: {
            /*bindings*/
        },
        templateUrl: 'path/to/example.html',
        controller: 'exampleController'
    };    
};
Была ли эта страница полезной?
0 / 5 - 0 рейтинги