Open PiotrSiatkowski opened 7 years ago
As an addition to discussion. Classes are a syntactic sugar around creating prototype chain. To be honest, I have quite scarcely seen prototype chain in angular application and now, suddenly, everyone wants to use prototypical inheritance pattern. If you would use prototypical inheritance regardless, use classes then. Otherwise, why bother?
Another thing strictly connected to classes is a 'new' keyword. I doubt if AngularJS uses that keyword internally while creating controllers or services. I saw model classes in SDK. Model objects are created using 'new' keyword. Class usage in this case seems to be fully justified.
All in all, I am of the opinion that we don't have to use them, not including situation, in which we would like to type *.prototype = something. Then I would gladly use classes as this is the very case why they were introduced to the language. Any thoughts?
I've been re-reading this series of articles that @terencemutizwa originally linked to which provides some good arguments against using classes (albeit not within the context of angular)
https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3
The question is how do we want to share functionality? His talk suggests factory functions are the best way of doing that. Then any object can be extended with multiple different types of functionality being added to it.
If you need a controller to be able to access some API it could be extended with an object that provides that specific behaviour while also being extended with another object that allows it to do some other well defined behaviour. On the other hand in a class hierarchy it is only possible to inherit from one parent.
I used to be in favour of classes simply because they look nice but I also didn't actually understand the fundamentals of JavaScript objects as thoroghly as I do now after a few months research.
The main point that needs to be answered is does a class add value
that is not available by extending objects?
Some very contrived examples of composition instead of inheritance. Observe the console in this fiddle https://jsfiddle.net/L0kecvzo/
// Using Object.assign
let obj1 = {
sayHello(name) {
console.log(`Hello ${name}`);
}
};
let obj2 = {
sayGoodbye(name) {
console.log(`Goodbye ${name}`);
}
};
// Wooo multiple extension :O
let obj3 = Object.assign({}, obj1, obj2);
console.log(obj3);
obj3.sayHello('world');
obj3.sayGoodbye('world');
// Factory function with private data.
let factoryFn = function() {
let privateName = 'privateName';
let identifySelf = function() {
console.log(`My name is: ${privateName}`);
};
return {
identifySelf
};
};
// Update existing object with more behaviour, this time from a function.
Object.assign(obj3, factoryFn());
obj3.identifySelf();
console.log(obj3);
// Cannot be accessed
console.log(obj3.privateName);
// Let's try Object.create
let obj5 = Object.create({
name: 'Roger',
identifySelf() {
console.log(`My name is ${this.name}`);
},
saySomething() {
console.log('roger dodger');
}
});
obj5.identifySelf();
obj5.saySomething();
// Not private
console.log(obj5.name);
// We can use a factory function for Object.create also
let obj6 = Object.create(factoryFn());
obj6.identifySelf();
// Not accessible
console.log(obj6.privateName);
Best thing about factory trait functions is that they give desired behavior and in many cases this behavior may be independent from the object which is extended. The less relations we have the more reliable and easy code we obtain.
One downside of using classes for components that I have found is when passing functions using the '&' binding.
import gridFilterTemplate from './bb_grid_filter.html';
class BBGridFilterCtrl {
constructor() {
this.inputValue = '';
}
handleFilterAction() {
if (this.inputValue === '') {
return;
}
const filterBy = `${this.column.key},${this.inputValue}`;
this.filterAction()(filterBy);
}
}
const bbGridFilter = {
controller: BBGridFilterCtrl,
controllerAs: '$BBGridFilterCtrl',
templateUrl: gridFilterTemplate.id,
bindings: {
column: '<',
inputValue: '<',
filterAction: '&'
}
};
angular
.module('BB')
.component('bbGridFilter', bbGridFilter);
If the component 'filterAction' was defined in was a class then when calling it from BBGridFilterCtrl you would lose the context of 'this' (it would be undefined)
Maybe there is a solution I am unaware of but in this case I changed the parent component to just be a function.
Here's another great article. It is part 3 of a series which I recommend reading in its entirety but in this part he goes deep into how the prototype system in the constructor pattern actually works. (Classes being syntactic sugar over this pattern)
Mark, this is something I was saying on Friday. Keyword this is quite dangerous. Basically it shouldn't be used at all in the context it is used. In JavaScript functions are not owned by any object. Object just holds them but does not own them.
https://jsfiddle.net/y71a0c2y/19/
In this fiddle it's easy to see how the use of function can devastate the very purpose of class. Arrow functions help but they cannot protect us from everything. Especially if we still want to use old plain functions not to mention we would like to be able to use partial functions or curried functions.
In fact by using '&' binding we basically send a curried function call. I have to admit I have no idea how unpredictable 'this' could behave in various situations I can think of.
@damianpudlo @ParanormalCoder @Joensg @DeanPalmer @terencemutizwa @TolgaCura @jonw-bb @WesleyPumpkinhead @flaviotulino @t-lardner @bogdansavin - Just a friendly reminder that we've set a deadline for commenting on this topic (Next retro - 20th September). Thanks to @PiotrSiatkowski @jthomas1 @MarkShakespeare for keeping the conversation going. Everyone else, try to throw your opinions in ASAP.
I'd like to add that whilst I've enjoyed using classes in components, the example that @MarkShakespeare has put up does seem a bit concerning. Anyone have any comments on that?
Either way, let's try wrap this badboy up so and take the first steps in our 'Angular Styleguide'.
Both styles has technically pros and cons, if I would look over the 2017 trends and the future, and to every programmer friend I know, and almost all the new projects around, they are using react, so inheritance style, so classes.
For bookingbug you can decide what you like, I am happy with both.
I highly recommend everyone read the articles I have linked to in previous comments for a deep dive into how JavaScript works under the hood, because there are no classes really ;)
It is one of the only truly object oriented languages because you can just create an object out of nothing {}
without the need for a class!
Edit: For the lazy:
https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3
I agree with Bogan that we should be keeping on top of trends and it seems classes will be sticking around for some time. Looking at the airbnb styleguide they seem to insist of classes. Also we need to think about why ECMAScript introduced classes. They must be moving towards a more OOP method of development. Shouldn't we be future-proofing out code by picking up this trend?
Also for the argument that classes exposes everything. I've just found out about static functions. This prevents the instance of the class having direct access to the functions whilst still giving us exposure to be able to run tests against. Keep in mind you can still access static functions using the constructor
class test {
static staticFunction() {
console.log('static');
}
publicFunction() {
this.constructor.staticFunction();
console.log('public');
}
}
let myvar = new test();
myvar.publicFunction(); // 'public'
myvar.staticFunction(); // ERROR
myvar.constructor.staticFunction(); // 'static'
There are cases which bring function as a must, however, I agree with orientation to classes. In the other words, from my personal view, I do not agree with orientation to functions, and having a minimum rate of use for classes.
As an example for the need of function, there is a case in: https://github.com/BookingBug/bookingbug-angular/blob/IMPL-3208/src/core/javascripts/components/angular-hal.js which made me to be happier to use function.
I think we should be wary of saying "lets do X because everyone else is doing it". There is nothing the class gives you that cannot be achieved with out it. But the inverse is not true (private data for example). In your example @TolgaCura the reason that function is accessible from the constructor is due to the prototype chain.
I hate to sound like a broken record but please read the articles I have posted and they will hopefully provide some more eloquent arguments for where I'm coming from. (this one in particular https://davidwalsh.name/javascript-objects-deconstruction)
You don't need classes for object orientation. In fact, as one of the articles points out, JavaScript is more object orientated than classical languages because you can create an object out of nothing instead of needing a class first. You only ever deal with objects, even functions are objects.
With reference to the angular-hal.js you could write that base cache class like this:
const BaseCache = {
data: [],
set (key, val) {
this.data[key] = val;
return val;
},
get(key) {
return this.data[key];
},
getObject(key) {
return this.has(key) ? {[key]: this.data[key]} : {};
},
del(key) {
delete this.data[key];
},
has(key) {
return (key in this.data);
},
delMatching(str) {
for (let key in this.data) {
if (key.indexOf(str) !== -1)
delete this.data[key];
}
}
}
// "Extend"
const DataCacheService = Object.create(BaseCache);
You actually write less than if you use a class! ;)
Tangentially I've spent a fair bit of time doing functional programming in Clojure over the past few years and that language provides no classes at all. You just deal with data and it works perfectly fine! I've worked with a huge Java code base in the past and the lack of needing concrete classes is one of the things I like most about Clojure and JavaScript.
I'd like to notice that the usage of static functions in Tolga's example may do more harm then good. I mean we should refrain ourselves from using language in the way it wasn't meant to be used. Static functions were mean to be used like they are in other languages for example:
MathToolkit.integrate()
In other languages static functions can alter static private fields, which I believe is not possible in JavaScript.
I don't know how react works but angular should be all about little independent modules (aka components). Module pattern depends on composition rather then inheritance.
Inheritance is the very thing that brought us scope dependency.
I think that following latest trends doesn't necessarily have to be the best thing as trends change very quickly. If people weren't using prototypes I don't know why they are suddenly using classes . It is the very same thing in fact (only more complicated internally as one of Jame's article pointed out).
In Mike's example please notice we define controller before component, whilst I believe that component definition is much more important and should be granted the first place in a code (in most cases we are not troubled by the controller logic which shouldn't be complicated but are looking for bindings and documentation).
I feel much safer to extend plain object or object with proxy then extending object with complicated prototype chain.
In language like TypeScript we can at least declare fields for classes. We cannot do this in Ecma Script thus I think it's some kind of self-limitation.
I'd like to mention that it's really easy to find real drawbacks of not only classes, but inheritance based design in the literature.
Before we make final decision let's just imagine how people will react to 20 line boilerplate constructor functions in every file ;)
Accessors in Java were once praised and deemed as a latest trend. Now everyone curses them and their creators xD
Very cool stuff in that article @jthomas1, it makes me think that classes are not needed at all but thats not for this discussion. I think using classes just as a way to identify usage. As in if its a class it has abstracted behaviour and is intended to be extended functions for anything else. I know it seems simple but nothing has really made me think that either way gives us any real advantages so technical feature discussions are kind of void at this point.
Very cool stuff in that article @jthomas1, it makes me think that classes are not needed at all but thats not for this discussion. I think using classes just as a way to identify usage. As in if its a class it has abstracted behaviour and is intended to be extended functions for anything else. I know it seems simple but nothing has really made me think that either way gives us any real advantages so technical feature discussions are kind of void at this point.
I've been playing devils advocate quite hard in the last week or so and it has been an interesting journey of discovery into the language :nerd_face: .
I think it is very important that we make a distinction between code style and fundamental application architecture. This is not a trivial style discussion like "use 2 spaces instead of 4 spaces", this is something that can have major ramifications down the line if we need to do refactoring.
This is also why I believe on this particular issue we should have discretion to use which method we feel is appropriate at a given time for the task we are trying to fulfil.
Yeah I guess I missed the point with how deep we are going with this guide. I don't think I have enough knowledge to have a constructive argument at this point.
Yeah no worries @DeanPalmer, I don't want to poo poo your suggestion of keeping it simple and I also don't want this discussion to go on forever. Pragmatism is the way forward.
I think the reason why it is adopted in React and Angular2 is because you tend to only ever extend once eg:
Class myComponent extends React.Component {
// blah blah blah
}
It would be quite rare for you to extend myComponent
again, at least when I have been using React or Angular2 I've never done that since you don't need to. You abstract all the shared logic into a service instead and that service can be injected in all the components that need it.
They could have quite easily implemented the framework thus:
const myComponent = React.Component.createComponent();
// or
const myComponent = angular.module('blah').createComponent() //look familiar? :D
Facebook and Google have decided that extending classes is how they want to do it but it all results in the same thing: myComponent
becomes an Object that has been provided with extra shared functionality that it didn't have before.
The argument against classical inheritance generally is that once you start creating a big chain the great-great-great-grandchildren end up with a whole bunch of stuff they don't need to know about.
It is much worse in Java and C# of course since they require that EVERYTHING be a class of some kind.
Yea I agree with @DeanPalmer. There's been a lot said in here which is great, but we still have people writing components, etc using classes, and others using functions. We need to address this ASAP. Function composition vs Class inheritance should be covered at another time IMO.
Carlos you mean controllers for components, right? I gave some technical advantages I think, but I guess we will end up voting anyway ;)
So, there are various approaches to writing controllers, services etc. Wanted to sum them up and write advantages and disadvantages so that we can discuss them.
Advantages:
Disadvantages:
Disadvantages:
Advantages:
Disadvantages
So naturally I am in favor of using old good functions right now. Syntax for me is still very readable and extensions can be performed using angular functions or other more powerful techniques like dynamic extensions and so on.
I am waiting for your opinion on this. I feel it should be one of the first things that we should decide on.