EmberJS 2018: Ember as a Component-Service Framework
Ember in 2018 is a truly amazing framework that has changed dramatically in the past few years, but you wouldn’t know it by looking at the guides! In this blog post, inspired by the call for blog posts to determine the Ember 2018 roadmap, I want to make a case for shifting the mental model that we use to understand and teach Ember to something more relevant in today’s frontend ecosystem. In doing so, I believe we will make Ember more approachable to newcomers and current users alike.
In the Beginning, There Was MVC
I’ve been a user of Ember.js since the pre-1.0 days, back when it was truly a Model-View-Controller framework. Back then, it was simple enough to tell new developers “Ember is MVC in the browser” and they would get it. There were obvious differences compared to server-side MVC frameworks - for instance Routes also played a massive role in the browser whereas they were usually a simple config file in most server environments - but the fundamental principles were the same at a very high level. The fact that Ember followed a well established pattern made it an attractive choice for users of frameworks like Ruby on Rails or Cocoa.
Then web components hit the scene, and changed everything. Polymer, React, Angular, etc. all began to coalesce around this shared notion of extending the DOM, creating in effect our own custom elements which could be used seamlessly with plain old HTML and CSS. This model took the frontend world by storm, and Ember went through a massive shift to keep up with it, adding Components, dropping Views and (most) Controllers, and fundamentally changing the way that most Ember apps were written.
The fact that Ember was able to make this transition while maintaining a clear upgrade path is what makes this framework so great, and is one of the reasons I still love the framework. It truly is a bedrock of stability without stagnation, and there are a ton of things that other #EmberJS2018 blog posts have highlighted which I’d love to see in next year’s roadmap. In no particular order:
- Angle Bracket Invocation
- Custom Components/Glimmer Components
- Routable Components
- Code Splitting/Packaging
- Native Classes
- First class NPM support
- Module Unification
- Fixing the Runloop
- Ember Data RecordData (and unlocking new data patterns in general)
However, if there’s one thing I think Ember needs more than anything else, its to update the way we think about the mental model. We need to be able to tell new users “Ember is a ___ framework” and have them get it.
New users who come to Ember today frequently complain that there are too many concepts to learn, that there’s a lot going on, a lot of magic, and it’s just too confusing. There are a lot of important concepts in the framework, and it’s hard to really distill it down into one of the existing TLAs (Three Letter Acronyms). Is it:
- Model-Controller-Route-Component-Service? MCRCS?
There’s a lot going on here, it could meet any of the first three framework types depending on how you define “view” and “controller” and “producer”, and our most accurate acronym, MCRCS, is just way too much. It sounds like a medical condition!
I would like to propose that Ember rebrand itself as a Component-Service framework. I believe that every major Ember concept falls into one of these two categories, and that teaching it this way will make Ember much more approachable to users coming from other frameworks and users who are new to frameworks alike.
Since the late Ember 1.x series, Components have become absolutely central to everything in Ember. The way that Ember implements components is core to the framework, and you can’t have Ember without Ember Components.
I would argue that every other concept is optional. That may seem like a ludicrous statement to people who have been using this framework since the Ember 1.x days, when the Router was the biggest selling point for Ember, but this is actually something that the core team has messaged about it in the past. Ember is gradually moving toward a “framework of packages” model, where users will be able to pick and choose the pieces of Ember that best suit their needs, and add on more core functionality as necessary. You can see the beginnings of this in the Splittting Ember Into Packages RFC, and in the EmberConf 2017 keynote from Tom and Yehuda.
However, when you remove everything else, at its core the thing that makes modern Ember Ember is its component system. You could theoretically build an entire app just by using components in
application.hbs, and never touch a single
model.js file — And I propose that this is exactly what we do for the new introduction to Ember guides.
Components are central to every other framework, and users from other communities are very familiar with them. We should use this to show off how simple Ember is at its core, by building up user’s understanding of components first. We can show off patterns that are common in other frameworks, and patterns that are unique to Ember, such as:
- Stateful/Stateless Components
- Container/Presentational Components
- Higher Order Components
- Contextual Components
- Yielding (and defining a public API for your component with yields)
Ideally we would cover all of these patterns and more in some amount of depth by building a simple app with them. This would also be a massive benefit to users who have never touched a framework before, and don’t know any of these patterns — it would teach them all of the different patterns they can use to build their app, which will help them even after we begin to add more layers of complexity to it with things like Routes, Controllers, and Models.
Before I stated that every major concept in Ember is essentially either a Component or a Service — let’s verify that claim:
- Routes — Managed by the Router Service under the hood, and theoretically optional. Ember alumnus Alex Matchneer has been working on a new constraint based routing service recently, which shows there is definitely room for growth and potentially even alternative implementations here.
- Models — Created and managed by the Ember Data
storeservice, which is automatically injected in many places but fully optional. Alternative data services exist, such as Ember Apollo, Ember Redux, etc.
- Controllers — These are one concept which does not neatly fit into either Component or Service, because Controllers are also tied to a template, and Services in general do not have a template. That said, Controllers may eventually be removed from the framework altogether, but you can think of them for now as “a service that is associated with a route”.
With the exception of Controllers, every major Ember concept neatly fits into the category of Service. Another way to think of it is, if we started Ember today as just a view layer with components, how would we implement all of the above functionality? As services!
What are Services?
Services (and more fundamentally, Dependency Injection) are Ember’s answer to the question of “how do we store long-term state?” Components are powerful on their own, but one of their greatest strengths is also one of their weaknesses — they are transient. As soon as a component is removed from the DOM, its state is gone forever.
If we were to build a complex app using just components, it would be a nightmare. We would have a single root level component that held all of our app state, and we would be passing that state down all the way to every single child. It would be a brittle mess, and every framework has this same problem.
Every framework has developed some answer to storing long-term state (some of them, like the Provider system in React-Redux, actually function a lot like services under the hood). Ember has gone with a battle-tested solution, Dependency Injection. This concept has a really great breakdown it the Glimmer docs, and we should bring that into the Ember docs as well to solidify users’ understanding of how the framework works. So, I should amend my previous statement — You can’t have Ember without Components or the Container!
Components are created by the container, and they can specify any services they need. Generally, services are just singleton POJOs, so every component shares the same instance of a service. This means that if one component mutates some state on a service, it’ll update everywhere else — and if a component is removed but needs to store some state, it can do this on the service.
Most of the details here can be glossed over here in the main portion of the guides. What we should communicate is that you can inject services into components to share state — and that there are a bunch of helpful standard services that are part of the framework.
Quick note: Services are very powerful, and there are plenty of anti-patterns here as well. Apps that evolve by haphazardly adding services whenever two components need to communicate will eventually become a mess of spaghetti, so we should definitely take some time to explain patterns and anti-patterns in services.
Standard Ember Services
Once users understand what services are and how they work, we can take them through each fundamental service and show them the details. The most important of these is the Router Service, which manages the url, browser history, and data loading for parts of your app. The guides can add routes and controllers to the app created with just components in this section, and demonstrate how they can be used to clean up a ton of code, showing the value of the shared solution.
We can then dive into Ember Data, and possibly other data solutions (or addons for Ember Data, once RecordData is implemented), and show users how they can standardize on a model layer instead of just using
fetch. Other types of useful services can also be discussed here as well, such as authentication solutions and feature flags systems, and other things provided by community addons.
Tying it All Together
In the end, I think modern Ember is simpler than ever. When viewing the framework through the lens of Component-Service, it’s easy to see how everything fits together, and how it can be pulled apart. Ember can be more easily pitched to people who prefer smaller solutions and incremental adoption, and it is easier to teach to new users. It also emphasizes our strengths as a framework — Components and DI — and de-emphasizes aspects which, while still relevant and necessary (Controllers), are confusing at first and make Ember appear weak at first glance.
Other #EmberJS2018 blog posts have also called out the Ember community for being somewhat of a bubble lately, and its no secret that other framework users think Ember is old-fashioned, dead, or just too complicated. By emphasizing what makes us similar and placing components front and center, we can both open ourselves up to the wider ecosystem, and show the wider ecosystem that Ember is actually a lot like the frameworks they already know and love, just a little different. By focusing on the things that we have in common, and de-emphasizing the things that are different, we position Ember as a stronger choice in the modern frontend landscape.
The future guides section of emberjs.com could look something like this:
Building a Todolist
What are Components?
Arguments & Actions
What are Services?
The Router Service
The Data Service
Creating, Updating, and Deleting
The Object Model
The best thing about this mental shift is that it’s low hanging fruit that everyone can help out with. This isn’t going to require deep changes to the Ember framework — it’s just changing the way we talk about it and teach it today!