Building an enterprise-grade app with AngularJS

by Dmitry Pashkevich

About Me

Dmitry Pashkevich

  • Internet citizen
  • Passionate about web apps
  • Love great user experience
  • Engineer at Lucid Software

Lucid Software

We're hiring!
www.golucid.co

Collaboration features

Real-time collaboration in Lucidchart

Team administration

Old team administration app in Lucidchart

Problem Statement

Lucidchart Team Admin App

Old team administration app in Lucidchart

Lucidchart Team Admin App

Old team administration app in Lucidchart

Lucidchart Team Admin App

Old team administration app in Lucidchart

Lucidchart Team Admin App

  • UI and code didn’t scale with growing functionality
  • Multiple places to manage account features
  • Poor performance on large team accounts

Solution: Redesign

New Lucidchart Team Admin App

New team administration app in Lucidchart

We decided to use Angular

Why Angular?

  • Didn’t think too much about it
  • Popular, actively maintained
  • Supports modern web app principles
  • Known to work with Closure Compiler

What is Angular?

AngularJS is a new, powerful, client-side technology that provides a way of accomplishing really powerful things in a way that embraces and extends HTML, CSS and JavaScript

Source

^ Evolution of existing Web Standards

What Angular gives us and how we use it

1. The MVC* pattern

* actually, it’s MVVM (Model-View-ViewModel)**

** actually, it’s Model-View-Whatever

1. The MVC* pattern

Model-View-Whatever schematic

2. HTML Templates

  • Not a new language
  • Easy to author & collaborate
  • Web Components ideology

2. HTML Templates


<context-menu menu-name="context-menu-options">
  <context-item
    ng-repeat="option in options"
    item-click="combo.selectedIndex = $index">
      {{option.label}}
  </context-item>
</context-menu>
                    

3. Two-way data binding


Your name: <input type="text" ng-model="firstName">


<p>Hello, {{firstName}}!</p>


<!-- That's it! No JavaScript written here! -->
                        
Model-View schematic

4. Routing (Deep Linking)

Deep Linking illustrated

4. Routing (Deep Linking)


$routeProvider.
  when('/', {
    templateUrl: '/view/MainView.html',
    label: 'Admin'
  }).
  when('/users', {
    templateUrl: '/view/UsersView.html',
    label: 'Users'
  }).
  when('/users/create', {
    templateUrl: '/view/CreateUsersView.html',
    label: 'Add New'
  })
  // …
                        

<a href="#/users/create">New user</a>
                        
                            
Deep Linking illustrated: new user button

5. Dependency injection


// Define account service
var AccountService = function() {
    // account service constructor body...
};

AccountService.prototype.getAccount = function() {
  // code for getting account...
};


// register it with Angular:
TeamApp.service('accountService', AccountService);

                        

// We need the account service
// in our apps controller!
var AppsController = function(accountService,
  samlService, gappsService) {

  // controller's constructor body...
  var account = accountService.getAccount();
}
                        

5. Dependency injection

  • Handles component discovery and import
  • Discourages use of globals
  • Loosely coupled modules

Recap: Angular Gives Us...

  1. MVC
  2. HTML templates
  3. Two-way data binding
  4. Routing
  5. Dependency injection

What Angular doesn't give us or what we've built

1. Code architecture

Code architecture

2. Code loading



<script src="/controller/LicensingController.js"></script>
<script src="/service/RestService.js"></script>
<script src="/model/UserRole.js"></script>
<script src="/model/User.js"></script>
<script src="/service/UserService.js"></script>
<script src="/model/License.js"></script>
<script src="/model/LicenseRequest.js"></script>
<script src="/model/Consumer.js"></script>
<script src="/model/Saml.js"></script>
<script src="/model/GAppsDomain.js"></script>
<script src="/model/Account.js"></script>
...
                        


<script src="/teamAdmin.js"></script>
                        

3. Data model


/**
 * @constructor
 * @param {lucid.services.RestLink} userLink
 */
lucid.team.model.User = function(userLink) {
    this.selfLink = userLink;

    /**
     * @type {?string}
     */
    this.username = null;

    /**
     * @type {?string}
     */
    this.email = null;

    /**
     * @type {?Date}
     */
    this.created = null;
}

/**
 * @param {string} productName
 * @return {angular.$q.Promise}
 */
lucid.team.model.User.prototype.getDocuments = function() {
    return lucid.team.model.UserDocuments.getUserDocuments(this);
}
                    

4. Server communication


{
    "uri": "https://localhost/accounts/2",
    "name": "My Team Name",
    "owner": "https://localhost/accounts/2/owner",
    "size": 54,
    "users": "https://localhost/accounts/2/users",
    "groups": "https://localhost/groups?accountId=2",
    "roles": "https://localhost/userRoles?account=2",
    "metadata": "https://localhost/accounts/2/metadata",
    "created": "2014-06-23T17:30:53Z",
    "updated": "2014-07-18T17:58:34Z"
}
                        

AccountService.prototype.accountUsers = function() {
  return this.getAccount()
    .then(function(account) {
        return account.users.get();
      }
    );
}


RoleService.prototype.assignUserRole = function(
userRow, roleName) {

  return userRow.user.roles.post({
    'role': this.getRoleID(roleName)
  });
}
                        

5. UI components


<section class="filters-container" id="filtersPanel">
  <h2>Products &amp; Users</h2>

  <combo-box model="currentProductFilter" options="productFilterOptions">
  </combo-box>
  <combo-box model="currentLicenseFilter" options="licenseFilterOptions">
  </combo-box>
</section>
                        

.component-combo-box {
  .component-pill-with-text;
  .box-sizing(border-box);
  line-height: 18px;

  &-primary-gray {
    .component-combo-box-variant(
      @component-primary-gray,
      @component-primary-gray-hover
    )
  }

  &-secondary-gray {
    .component-combo-box-variant(
      @component-secondary-gray,
      @component-secondary-gray-hover
  }

  ...
}
                        

Shipping to the world

Angular.js features

Takeaways

Should I use Angular?

  • Probably
  • Not a monolithic framework
  • What is your current stack?

So how do I make a big app?

  • You don't!
  • Develop small modules
  • Refactor the code as you go
  • Release parts of your app regularly Release your app part by part

Bonus: how do you make it fast?

  • It probably already is!
  • Identify the bottleneck
  • It may not be inside Angular
    (e.g. network communication)

Thanks!

Lucid Software