Вместо использования директив мы можем использовать компонент в тех местах, где нам не нужно манипулировать DOM. Но я не могу найти руководство по стилю для этого в angularjs 1.5.
Какие предложения есть у сообщества по этому поводу?
Я абсолютно хочу раздел компонентов
Мне так нравится:
(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, если вам интересно.
Это тоже самоуверенно, но я предпочитаю, чтобы это было просто. Нет 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
- лучший вариант здесь, и он соответствует значениям по умолчанию, упомянутым в ваших примечаниях выше.
Другие вещи, которые следует учитывать,
this
, vm
, $ctrl
)this
(см. # 2 выше) как обрабатывать новые закрытия (например, self) ... yukЭто лишь некоторые примеры, которые я извлек из множества способов, которые я видел и использовал .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
чтобы действительно получить односторонние данные (т.е. к родителю). Несколько вопросов:
this.dataService.loadData()
внутри хука $onInit
? Насколько я понимаю, поскольку dataService
объявлен как зависимость как параметр контроллера, механизмы внедрения зависимостей angular обеспечат доступность такой (одноэлементной) службы при создании объекта контроллера. Имеет ли смысл использовать часть require
API компонента / директивы, чтобы вместо этого продемонстрировать гарантию $onInit
?prototype
из ButtonListController
если вы можете так же легко добавить эти методы внутри самой функции конструктора без каких-либо потерь в поведении, особенно учитывая, что ButtonListController
не будет использоваться как часть некоторой прототипической цепочки наследования. Вы что-то пытаетесь здесь подчеркнуть?@texonidas :
$inject
ables" к функции-конструктору, которая ссылается на них как на параметры?exampleComponent()
если вы можете просто передать конфигурацию компонента (литерал объекта) непосредственно в качестве параметра методу component
? Среди прочего, вы вводите переменную с именем component
, только чтобы вернуть ту же переменную и метод с тем же именем.@jbmilgrom
Гораздо проще контролировать свои модульные тесты, если вы не помещаете «настоящую» работу в конструктор. Если ваш конструктор действительно работает, например, при загрузке, то невозможно предотвратить это (или задержать его) внутри ваших тестов. С дополнительным усложнением системы DI, выполняющей создание экземпляра, вы теряете еще больше контроля над временем.
Учитывая, что этот компонент может использоваться много раз в одном приложении, более эффективно с точки зрения памяти помещать в прототип методы, которые будут совместно использоваться экземплярами.
@petebacondarwin спасибо за ответ, имеет смысл.
Объявляя методы в области видимости функции конструктора, которые ссылаются на переменные, также объявленные в области видимости функции конструктора, вы можете получить какое-то частное состояние (через закрытие). Для контроллеров, которые могут делать слишком много, это может быть полезно, помимо прочего, для различения состояния, которое потенциально может быть изменено непосредственно в шаблоне, и состояния, которое, несомненно, не может. Кажется, это невозможно в вашей настройке. Вместо этого вы привязываете все состояние к this
и делегируете их службам, если требуется конфиденциальность?
@jbmilgrom
$ Inject нужен для безопасности минификации. Это не обязательно, и если бы это должно было войти в руководство по стилю, я бы удалил его, так как он подробно изложен далее в разделе, посвященном минификации.
Я использовал тот же синтаксис, который руководство по стилю рекомендует для сервисов:
/* 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'
};
};
Самый полезный комментарий
Лично я использую следующий стиль: