Angular model objects with JavaScript classes

Angular model objects with JavaScript classes

The missing piece in AngularJS

Unlike Backbone and Ember, AngularJS does not provide a standardized way to define model objects. The model part of MVC* in Angular is the scope object, which is not what we mean by model objects. A model object is the JavaScript version of a class instance. Developers familiar with object oriented programming will feel right at home using model objects. I’m a big fan of the mix of OO and functional programming that JavaScript provides.

* Actually I think of AngularJS as a ViewModel-View-Controller framework, since $scope is better described as a ViewModel rather than a data Model.

Classes are blueprints for instantiating objects, which have a constructor and can have public, private and static properties as well as public, private and static methods. There are various ways in which you can define a class in JavaScript. I prefer using a named function, with methods added to the prototype. The main reason for this is the naming, which gives you the option to use the instanceof operator to check its type. Using the prototype has the advantage of sharing functions between objects of the same class, saving memory.

Whatever style of class definition you prefer, hooking them up to AngularJS is pretty straightforward. I prefer to use the factory function, but you can use the service or provider functions instead if you like. Here’s a complete model definition:

.factory(‘User‘, function (Organisation) {
 
  /**
   * Constructor, with class name
   */
  function User(firstName, lastName, role, organisation) {
    // Public properties, assigned to the instance (‘this‘)
    this.firstName = firstName;
    this.lastName = lastName;
    this.role = role;
    this.organisation = organisation;
  }
 
  /**
   * Public method, assigned to prototype
   */
  User.prototype.getFullName = function () {
    return this.firstName + ‘ ‘ + this.lastName;
  };
 
  /**
   * Private property
   */
  var possibleRoles = [‘admin‘, ‘editor‘, ‘guest‘];
 
  /**
   * Private function
   */
  function checkRole(role) {
    return possibleRoles.indexOf(role) !== -1;
  }
 
  /**
   * Static property
   * Using copy to prevent modifications to private property
   */
  User.possibleRoles = angular.copy(possibleRoles);
 
  /**
   * Static method, assigned to class
   * Instance (‘this‘) is not available in static context
   */
  User.build = function (data) {
    if (!checkRole(data.role)) {
      return;
    }
    return new User(
      data.first_name,
      data.last_name,
      data.role,
      Organisation.build(data.organisation) // another model
    );
  };
 
  /**
   * Return the constructor function
   */
  return User;
})

While the code and the comments explain a lot, there’s a few things to note here. Firstly, the name of the constructor (class name) is technically unrelated to the name of the Angular factory. You could give the factory and the constructor different names, but that will probably lead to a lot of confusion, so I recommend keeping these two in sync. In fact, minification would result in the class name to be changed to something short, while the factory name, being a string, remains the same. If you do choose to diverge here, remember that the factory name is what you instantiate and need for the instanceof check, while the class name is what you see when you log an instance to the console.

Secondly, wrapping classes in Angular factories will provide you with the option to use dependency injection. The model object itself can be injected elsewhere in your application, and you can inject other stuff into the factory. In the example I’ve injected Organisation, which is another class like this one. I strongly recommend to never inject anything but other models or filters in your models, otherwise you will end up with circular dependencies and all hell breaks loose. If you have a need to include services in your models, you’re doing it wrong. You can often avoid it by moving logic from the service into the model or a filter, or your model is trying to do too much already and logic should be moved into a service.

Using models with services

The most common place to create model objects is in services. I often find myself writing services for most of the models in my application. This is because models usually reflect a data resource (in RESTful terms), so it makes sense to have a service which acts on the API endpoint for this resource. For example, a basic OrganisationService:

.factory(‘OrganisationService‘, function (API, Organisation) {
  return {
    get: function () {
      return API
        .get(‘/organisations‘)
        .then(Organisation.apiResponseTransformer);
      });
    }
  };
});

The API service is just a wrapper for $http and returns a promise with the data of the response instead of the entire response (including headers and such). The most interesting to note here is the static methodOrganisation.apiResponseTransformer, which is passed as the callback function to then(). The goal is to have all of the organisations returned by the API to be mapped to the Organisation model. This allows you to verify the response data and enhance it with additional properties and methods. An apiResponseTransformer may look like this:

Organisation.apiResponseTransformer = function (responseData) {
  if (angular.isArray(responseData)) {
    return responseData
      .map(Organisation.build)
      .filter(Boolean);
  }
  return Organisation.build(responseData);
};

This code would be located in the class definition of Organisation. A nice thing about promises is that you can chain multiple calls to then(), so you can use several response transformers to handle complex response data (when dealing with a not-so-RESTful API for example). The result is still a promise, so it’s easy to use in your route resolve functions.

Extending classes

An important concept in object oriented programming is inheritance. Unlike true OO languages such as Java, JavaScript does not have class inheritance but uses prototypal inheritance. The main difference is that in JavaScript, you manually clone the prototype object from one instance to another (or link them by reference), instead of declaring inheritance on the class definition. There’s a few options you can explore:

/**
 * One-way reference from subclass to superclass (instance)
 * Most of the time this is what you want. It should be done
 * before adding other methods to Subclass.
 */
Subclass.prototype = new Superclass();
 
/**
 * Two-way reference
 * Superclass will also get any Subclass methods added later.
 */
Subclass.prototype = Superclass.prototype;
 
/**
 * Cloning behavior
 * This does not setup a reference, so instanceof will not work.
 */
angular.extend(Subclass.prototype, Superclass.prototype);
 
/**
 * Enhancing a single instance
 * This could be used to implement the decorator pattern.
 */
angular.extend(subclassInstance, SuperClass.prototype);

I’m not going to go into the details of implementing inheritance between model objects. There’s plenty of resources available online explaining the specifics. One reason is that I think this should be done sparingly, when it’s really the single best solution. Prototypal inheritance is a powerful thing, but it’s misunderstood by many. Chances are that using inheritance will only complicate things for you and your fellow developers, especially when they aren’t all JavaScript gurus. Most of the time it’s better to accept a little code duplication between models or move the logic into a filter or service. Also, you should prefer composition over inheritance.

时间: 2024-10-06 18:21:07

Angular model objects with JavaScript classes的相关文章

Object.create(): the New Way to Create Objects in JavaScript

There are a lot of ways to create Objects in JavaScript, perhaps even more to integrate inheritance into them. Just when you thought that you've seen every possible way to create JS objects, I'm here to announce that there's yet another: the new Obje

ASP.NET MVC - loop model data in javascript

Key: razor syntax using @: before the js variable in c# code block Example: var chartData = []; @for(int i=0; i < Model.ModuleDetails.Count; i++) { @: chartData.push(@Html.Raw(Json.Encode(Model.ModuleDetails[i].ChartData.ToArray()))); } reference: ht

JavaScript中的Objects

JavaScript中的Objects 在JavaScript中,创建Objects有两种方式,declarative (literal) form和 constructed form. 首先,声明式如下: var myObj = { key: value // ... }; 构造式如下: var myObj = new Object(); myObj.key = value; 声明式与构造式创建的对象完全相同,只不过构造式的需要一个个地添加添加属性,一般情况下我们都是采用声明式的创建对象. J

Learning JavaScript Design Patterns The Constructor Pattern

In classical object-oriented programming languages, a constructor is a special method used to initialize a newly created object once memory has been allocated for it. In JavaScript, as almost everything is an object, we're most often interested in ob

How difficult is it to create a JavaScript framework?

分享来自 quora 的一篇文章 https://www.quora.com/How-difficult-is-it-to-create-a-JavaScript-framework This mostly depends on your JavaScript skills, but as you said, that you are using a lot of JavaScript it won't be that difficult... There are two different w

Learning JavaScript Design Patterns -- A book by Addy Osmani

Learning JavaScript Design Patterns A book by Addy Osmani Volume 1.6.2 Tweet Copyright © Addy Osmani 2015. Learning JavaScript Design Patterns is released under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license. It

JavaScript 框架------------AngularJS(上)

一.简单了解一下AngularJS AngularJS 是一个 JavaScript 框架.它可通过 <script> 标签添加到 HTML 页面. AngularJS 通过 指令 扩展了 HTML,且通过 表达式 绑定数据到 HTML. AngularJS 是一个 JavaScript 框架.它是一个以 JavaScript 编写的库. AngularJS 是以一个 JavaScript 文件形式发布的,可通过 script 标签添加到网页中: <script src="Js

Introduction to Object-Oriented JavaScript 转载自:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript

Introduction to Object-Oriented JavaScript IN THIS ARTICLE JavaScript review Object-oriented programming Terminology Prototype-based programming JavaScript Object Oriented Programming Namespace Core Objects Custom Objects The Class The Object (Class

精通 Angular JS 第一天——Angular 之禅

简介 Angular JS是采用JavaScript语言编写的客户端MVC框架,它为业界带了重大的变化,包括对模板化的创新实现,以及数据的双向绑定,这些特性使得它强大而易用.它可以用来帮助开发者编写单页面应用,尤其适合编写有大量CRUD操作的,具有Ajax风格的富客户端应用.大多数开发者认为,与其它框架相比,AngularJS明显缩减了项目所需的代码量. 2012年6月,Angular JS正式发布1.0版,在各种客户端MVC框架中,属于后起之秀.AngularJS主页(http://www.a