Preparing for Angular 2 / Testing ES6: No scope, no page controllers. Instead: Classes, MVC'ish Components (and Angular 1.4 bindToController)


#1

I was checking Angular 2.0 and the roadmap for 1.X. - tried to test how one might use some patterns already now, with Ionic.

After a few hours, one pattern of conventions emerged using Angular 1.4 and ES6 (+Traceur).
As this is not my speciality, I’d like to get some comments from those who have experience with this already!

There is a link to a CodePen sample in the end.

Components + services.

1) Creating components, a tree of them.
By creating ES6 Classes for everything. Not injecting or using $scope anywhere. No app- or page-level controllers.

Exchange of data between the components: children and parent might need to do it directly, others: creating a service.

The first class might be a very lean (bootstrap only) root of the component tree, class App, for instance. Initializing & storing only the top-level nodes of the tree here - the ones to throw in the html file (as directives). The directives may then instantiate children directives to create a hierarchy.

For each component, one might use a pattern like this:

  • const COMPONENTMODEL - A POJO to copy an instance from (for the data). Easy to save and load to a noSQL DB or use a REST API for the backend.
  • class ComponentController - All methods go here. Creates and maintains a copy of the COMPONENTMODEL.
  • class ComponentView - Basically just the html snippet plus bindings to the Controller.

I also threw in (for each component):

  • const COMPONENTSESSION (for the ComponentController to copy and use - a POJO that is not persisted, but is related to the controller and it’s state)
  • And a link to the parent/container Component - so it is easier to traverse the hierarchy and do (stupid?) things like call the parent methods from the child.

2) Binding it to Angular services and directives.
Technically, each View becomes an Angular Directive, bound with an Angular Controller (not for the page, but for the component), manipulating the POJO Model - and optionally keeping track of the component state in the Session POJO.

The CodePen shows also, how one might load the Model and the Session from the View using html attributes.

Directives combining all:
A directive can use an empty scope, define how to bindToController (Angular 1.4) and skip scopes by using controllerAs. A directive then becomes like this:

class ComponentView {

  constructor() {
    this.scope = {};
    this.restrict = 'E';
    this.controller = componentController;
    this.controllerAs = 'component';
    this.bindToController = {
      data: "=",
      session: "=",
      parent: "="
    };
    this.template = `
      <div ng-if="component.session.editing">
        {{::component.data.somevalue}}
      </div>
      <child-component></child-component>
      ...
    `;

Model (and Session) as constants to be copied by the controller:

const ITEMMODEL = {
  id: "",
  text: "",
  ...
};

const ITEMSESSION = {
  new: true,
  editing: true,
  ...
}

The ComponentController:

class ComponentController {

  constructor() {       
    if (!this.data) 
      this.data = Object.assign({}, ITEMMODEL); 
    if (!this.session) 
      this.session = Object.assign({}, ITEMSESSION);
  }

  (then methods)....

and binding that with Angular, note that .directive does not accept a class, it expects a function:

 .controller('componentController', ComponentController)
 .directive('componentSelector', [() => new ComponentView()]);

3) Misc: Using ES6 Modules, Angular dependencies, …
Further editing to be done: ES6 modules enable splitting the code into components on the file level.
Did not test yet.

Angular dependencies on the other hand:

class DB {

  constructor($http, $timeout) {
      this.$inject = ['$http', '$timeout'];
      this.$http = $http;
      this.$timeout = $timeout;
...

Then, later:

      this.$timeout(function() {
        console.log("Service: DI works!")
      }, 1000);

For a Service, that’s all that’s needed. Introducing a service to Angular:

.service('DBService', DB)

Controllers can be introduced likewise. For Directives, injecting needs to be added here, too. It looks a bit funny:

.directive('componentSelector', ['DBService', (DBService) => new ComponentView('DBService')])


LINK TO THE CODEPEN:


#2

Web Components:
If anyone wants to try out components the easy way / online, no installations - I made a Codepen collection with starters for a few alternatives.
Angular 2.0 and Aurelia are iframes to Plunker, as they require several .js and .html files.

Available starters currently for: Angular 2.0 preview, Angular 1.4 RC2 with ES6 via Babel (possible to create components 2.0 style), Polymer and of course Aurelia.

Feel free to fork from: http://codepen.io/collection/XPYmZa/