Instead of using directives we can use component on those places where we dont need to DOM manipulate. But I cant find any styleguide for this in angularjs 1.5.
What suggestions does the community have for this?
I absolutely want a component section
I like it this way:
(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');
}
}
}());
Alternatively move component registration to the bottom:
(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);
}());
The problem is that I would prefer using the new $ctrl reference instead of vm which makes the vm-pattern inconsistent. So either switch completely to $ctrl or simply add controllerAs: 'vm' to the component settings object to be consistent with the rest of the style guide :)
I like the second way better. I'd also consider putting methods on a prototype to make it easier to migrate to ES6 classes later. I also agree it's time to switch to $ctrl
as it's the default and not having to declare controllerAs
reduces the boilerplate needed.
I prefer the first way fwiw :grin:
(without vm
of cource :stuck_out_tongue:)
First method is my presonal fav. I'd add to both controllers:
// other vm vars/assigns here
vm.$onInit = activate;
// functions
function activate() {
// initialization magic here
}
of course we can change the name of activate
function to init
or onInit
etc.
This is a bit more opinionated and ES2016 but for my components I do it like that :
(function() {
'use strict';
/*
<active-places data="">
</active-places>
*/
const componentConfig = {
bindings: { data: '<' },
templateUrl: 'components/active-places/active-places.widget.html',
controller: ActivePlacesController,
};
/* @ngInject */
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);
}());
I like to have a copy-pastable snippet at the top of my file, that make me gain a lot of time. And it is more explicit and useful when developing in a project than the module and component declaration AMHO.
I also like the Object.assign
(ES2015 could use angular.extend
) call to bind all the methods and props to the component scope and also shave the file of all those little vm.
.
I use ES6 shorthand notation for objects when I can.
I also dropped the vm
capture variable for the this
use. Since ES2015, it is way more usable and since NG1.5+ I always find a way not to use $watch. Bonus effect, I learn to use it instead of running away from it.
Here is a revision using ES6 if you are interested.
This is also opinionated but I like to keep it simple. No vm, just $ctrl. No division of what to be exposed to the view. Thoughts?
(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);
};
}
})();
I helped push the team for a .component
feature for A1 ... and I am thrilled they did this.
I also voted very publicly for vm.*
to continue in the A1 version of .component
. But the community voted on the issue to go with $ctrl
. In the spirit of the style guide, consistency is most important. As such, I think $ctrl
is the best option here and it is in line with the defaults mentioned in your notes above.
Other things to consider are
this
, vm
, $ctrl
)this
(see # 2 above) how to handle new closures (e.g. self) ... yukThese are just some examples I grabbed out of the many ways I have seen and used .component
thus far.
Let's keep the chat going on these and any other relevant ones.
I am keen on the 1st format that declares the component at the top. The reason is that the 2nd version introduces a new variable activePlaces
, the name of which would need to be debated and which adds more complexity to reading the file.
I would also be keen to put the controller methods on the prototype. Unlike services, you can get multiple instances of controllers, especially if used in components, and it would seem more favourable not to be creating new copies of every method per instance.
Since components require a controller as, the default of $ctrl
is my preferred option, you tend not to get context-free function calls so often of the controller methods.
I have always been calling the function [ComponentName]Controller
since it is a controller for a component. I would recommend this, despite the fact that when we move to A2 these names would change - it would be a simple refactoring.
It is important in components to make use of the lifecycle hooks, rather than the controller constructor:
constructor
- only simple initialisation of properties, such as attaching injected services to properties. No real work should be done here, since going forward none of the bindings will be assigned here.$onInit
- at this point the bindings and any required controllers have been assigned. This is where any real initialization work should be done.$onChanges
- this is called whenever any of the input (<
) and interpolation (@
) bindings change. It is important to realise that mutation of input objects does not trigger the interpolation. But this is a good place to put code that reacts to changes to the inputs, which saves on creating watches. I would suggest that if you ar going for a "one-way data flow" approach then this is a good place to make a copy of the input objects as they change so that you don't inadvertently mutate an object that is passed.$onDestroy
- the only reason for this method is to release or close long running resources that have been acquired or opened during the life of the component. Note that this hook does not get called if you manually destroy the component's isolate scope, nor if you manually remove the components element from the DOM. It only gets destroyed if the containing scope, the scope in which this component lives, is destroyed.For output bindings I have been following the approach of having a handler method buttonClick(button)
that actually triggers the output onButtonClick({$event: button})
. And that the payload for the output is always put on the $event
property. This means that on the outside you are able to do <button-list on-button-click="$ctrl.doStuff($event)">
which is similar to what you see in Angular 2.
So it all tends to look like this:
(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 I like what you have with a few changes.
I prefer not to embed the { }
for the configuration for the component and instead to use a variable. I find this easier to read and debug. The name is irrelevant to me ...
var componentConfig = { ... };
angular
.module('app.shared')
.component('buttonList', componentConfig);
My larger concern is about the use of this
and how functions can access the properties of the controller. The controller will have state ... yours has $ctrl.buttons
in the template and I assume we'd have a this.buttons
in the controller. When we create functions (prototype or otherwise) we have closures and some of those have their own this
reference. This happens, for example, when we use $timeout()
. How are you seeing these being resolved?
I had this problem in my example with the promise then handler. I chose to use .bind
yeah, but that is not fun :)
that's another reason we used vm
in A1 controllers
Well you can always go with $ctrl in that case
On 14 Sep 2016 19:05, "John Papa" [email protected] wrote:
yeah, but that is not fun :)
that's another reason we used vm in A1 controllers
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/johnpapa/angular-styleguide/issues/766#issuecomment-247103228,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAA9J5DE4qIUEOEuPEIPLKobfDKT_Bbjks5qqDeEgaJpZM4Jyo-5
.
Right now I'm doing it like this (going with $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 );
}
}
} )();
What do you guys think?
I guess one could be inspired from Todd Motto styleguide ?
https://github.com/toddmotto/angular-styleguide
Yes but that's ES2015. What happens with people still stuck with pre-ES2015?
Personally I've been using the following style:
(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'];
/* @ngInject */
function ExampleComponentController(exampleService) {
var vm = this;
/* generic controller code */
}
})();
What about providing unique prefix for component as in directive? Is it considered necessary?
@petebacondarwin I like your example as it emphasizes one way data bindings and the need to copy them in the $onChange
lifecycle hook to truly achieve one-way data (i.e. state that a child may not change in a way that propagates to parent). A few questions:
this.dataService.loadData()
inside of the $onInit
hook? As far as I understand it, since dataService
is declared as a dependency as a controller parameter, angular's dependency injection mechanisms will ensure such (singleton) service is available when the controller object is constructed. Would it make sense to make use of the require
part of the component/directive API to showcase the $onInit
guaranty instead?prototype
of ButtonListController
when you can just as easily add these methods inside the constructor function itself without any loss in behavior, especially considering that ButtonListController
will not be used as part of some prototypical chain of inheritance. Is there something you're trying to emphasize here?@texonidas :
$inject
ables" to a constructor function that references them as parameters?exampleComponent()
when you can just pass the component configuration (object literal) directly as a parameter to the component
method? Among other things, you're introducing a variable named component
, only to return the same variable and to method by the same name.@jbmilgrom
It is much easier to control your unit tests if you do not put "real" work in the constructor. If your constructor does work such as loading then it is impossible to prevent this from happening (or delaying it) inside your tests. WIth the added complication of the DI system doing the instantiation you lose even more control over the timing.
Given that this component could be used numerous times in a single app it is more memory efficient to put the methods, which will be shared between instances, on the prototype.
@petebacondarwin thanks for the response, makes sense.
By declaring methods in the scope of the constructor function that reference variables also declared in the scope of the constructor function, you can gain a kinda private state (through a closure). For controllers that may be doing a little too much, this can be helpful for, among other things, distinguishing between state that could potentially be mutated directly in the template and state that undoubtedly cannot. That appears to not be possible in your setup. Instead, do you bind all state to this
and delegate to services if privacy is desired?
@jbmilgrom
The $inject is there for minification safety. It's not absolutely required, and if this was to go into the styleguide, I would remove it, as it is laid out explicitly further on in the section on minification.
I went with the same syntax the styleguide recommends for services:
/* recommended */
function dataService() {
var someValue = '';
var service = {
save: save,
someValue: someValue,
validate: validate
};
return service;
////////////
function save() {
/* */
};
function validate() {
/* */
};
}
in that you explicitly name the variable, then return it.
I am using an aproach similar to texonidas's, but decided to keep the default $ctrl in the html (not using controllerAs).
file name: 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';
}
.
.
.
};
Should the component be registered with the module?
My teams interpretation of the v1 style guide is that all controllers should be registered with the module and referenced by the name.
.module('app.moduleName')
.controller('exampleController', exampleController)
.component('exampleComponent', componentConfig());
function componentConfig () {
return{
bindings: {
/*bindings*/
},
templateUrl: 'path/to/example.html',
controller: 'exampleController'
};
};
Most helpful comment
Personally I've been using the following style: