Is it possible to build an Aurelia app without using the class syntax? Writing in ES6 is still desired, but can we write it in such a way to skip the whole class layout, and make it more similar to how AMD modules behaved with Durandal?
Show me what you want to write. I'm pretty sure, but I want to see what you are thinking.
What would be the class view model in this case is something as simple as this
import {GetRoutes} from 'settings/routeConfigs/settings';
export class Navigation {
sections = [];
activate(){
this.sections = GetRoutes();
}
}
What would be nice to write (and the code might be slightly off, but to just give an idea)
import {GetRoutes} from 'settings/routeConfigs/settings';
let sections = [];
let viewModel = {
sections: sections,
activate: ()=>{
sections = GetRoutes();
}
}
export default viewModel;
We can add support for default exports. That's easy enough. However, here you've got an instance and you aren't using DI at all. That presents a bit of a problem. I'd need to think through how we could address this. Most likely, we'd have a special case, where this only worked for default exports. If the default export was not a function/class we would generate a function to return the instance and attach appropriate metadata to it.
If you are interested in working on a PR for this, the place the logic would probably need to go is inside the ModuleAnalyzer. Special logic would probably go just before this line: https://github.com/aurelia/templating/blob/master/src/module-analyzer.js#L240
Can you share why you want to do this?
Good question. What the user wants isn't always what they need ;)
What I'm trying to accomplish is to provide minimal changes to some developers in writing new code. We have developers who have programmed AMD style modules in ES5, and the class syntax can be a big difference in style.
However, there are some nice features that we like in ES7, in particular the import/export functionality, as it makes readability much better. So, is this even possible, to write a module in a non-class syntax? If it turns out that there would be a lot of overhead to the coding developer to support this, it wouldn't be worth it.
My understanding of DI is also just coming to fruition, and my understanding is that the trouble with writing without classes is that we wouldn't plainly be able to get the same instance of whatever we were trying to inject. So that's a use case I'm not sure about yet, because we will need to be able to get to our instanced API client that we've used DI before to get at...
@AStoker my two cents would be to show the devs how much simpler it is to use the new syntax compared to the AMD syntax. It really is a rather well thought out improvement over both the AMD and CJS formats.
If they still fight you, you can show them how it is possible to write the same code that the transpiler would write when transpiling from ES2016 class+import/export+decorators style to AMD style. I've included the Users class from the skeleton below as an example:
ES2016+ Style:
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import 'fetch';
@inject(HttpClient)
export class Users {
heading = 'Github Users';
users = [];
constructor(http) {
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
this.http = http;
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
}
AMD Style:
define(['exports', 'aurelia-framework', 'aurelia-fetch-client', 'fetch'], function (exports, _aureliaFramework, _aureliaFetchClient) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Users = undefined;
var _dec, _class;
var Users = exports.Users = (_dec = (0, _aureliaFramework.inject)(_aureliaFetchClient.HttpClient), _dec(_class = function () {
function Users(http) {
this.heading = 'Github Users';
this.users = [];
http.configure(function (config) {
config.useStandardConfiguration().withBaseUrl('https://api.github.com/');
});
this.http = http;
}
Users.prototype.activate = function activate() {
var _this = this;
return this.http.fetch('users').then(function (response) {
return response.json();
}).then(function (users) {
return _this.users = users;
});
};
return Users;
}()) || _class);
});
At the end of the day, learning the new style syntax would make their jobs easier compared to always adding the AMD boilerplate.
If someone ask me to write such code today, I'll surely quit the job... :stuck_out_tongue_winking_eye:
To add my two cents to the discussion there are plenty of developers that completely eschew the idea of 'classes' in JS. If i need a bunch of the same kind of object, I can just create them with factory functions a la:
`
const createViewModel = (dependency1, dependency2) => {
let _vm = {
prop1: dependency1,
prop2: dependency2("foo"),
youGetTheIdea: 42,
};
_vm.someMethod = () => {
};
return _vm;
};
`
I, and many devs I know write all our code this way, only using new and this where forced by syntax. In the rare event we actually need the speed boost and memory we can always fall back on that, but I've written plenty of enterprise code with nary a class or 'new'. Even Doug Crockford is writing js like this these days, so this isn't a marginal constituency or use case.
It would be nice for Aurelia to support developers who want to write in this style as those who do tend to feel rather strongly about the point.
@themooserooster You _should_ be able to write that code today. Have you tried it? You will have to provide the metadata to Aurelia's DI so it can actually call your functions with the correct arguments. That works the same as anything else.
I'd love to try it! Just not sure what the syntax for that would be... I haven't been able to find it in the docs and would like to contribute a PR on this point if need be.
I'm in the process of porting an app to ES6/ Aurelia, for context.
I'm not sure if you can use the @inject decorator legally in this case, but you can try putting it on the function. If that doesn't work, you can do this:
createViewModel.inject = [Dependency1, Dependency2];
@themooserooster There's an example at Stack Overflow here.
Any new news on the topic..? I want to code with aurelia in a functional way. Using factories instead of classes. This would allow for a more prototypal oo coding style. I tried the example but can't get it running for ViewModels.
To make this work, what do I need to do in aurelia..? What are the metadata I need for this that you talked about..? Where do I find the code that is responsible for making classes work but factories not?
@dominiquejuergensen I'm not sure how you would go about doing functional MVVM. MVVM and functional programming really don't mix very well, since MVVM depends on mutating the state of the VM, while functional programming relies heavily on immutable state. This isn't a knock on either paradigm, it's just they don't really work well together.
But I'm willing to be proven wrong.
@AshleyGrant its not about a specific design pattern. More about style and a way of thinking. As @themooserooster points out there are people out there that don't want to code with "class"es. Also it's in part possible as @derekgreer shows with the link to an example at stack overflow. The example works as CustomElement but not as ViewModel. The error I get when trying says that no view strategy can be found. So I assume that I need to implement a view strategy for that..?!?
I want to make this possible and i'm willing to dive into the aurelia code to make the changes. I'm asking for help with that. Maybe you can point in the right direction where I have to look.
@dominiquejuergensen take a look at the stack overflow post you mentioned (re-posting below for convenience) as well as the documentation and stackoverflow posts for the Factory.of resolver.
@EisenbergEffect @AStoker Is this still desired ? Wonder how we create multiple view models ? Is it gonna be Object assign ?
To be honest, this one slipped off my radar. I got the other coworkers on board with classes, but they still complain about having to write this everywhere (don't entirely blame them). Danyow's answer actually does a good job of answering this question. However, you do bring up a good point @bigopon, I'm not sure how this would handle multiple instances of a vm. If I have a repeater for a custom element, would all those custom elements have their own instance of the vm (as it would with the class structure)? If so, then I'm going to mark this as closed.
If insisted, i think it could be a default export with signature createViewModel
export default createViewModel() {
var vm = {
activate() {
vm.doThings();
},
// ....
}
return vm;
}
createViewModel.inject = [];
Or we can use WeakMap to link it to a fake class, then do something. Doable, but not sure if still desirable.
@AStoker
Edit, just read the whole thread again, somebody already suggested that ...
Nice read
but maybe can be closed
@AStoker ?
Most helpful comment
If someone ask me to write such code today, I'll surely quit the job... :stuck_out_tongue_winking_eye: