ng-admin 0.8 Is Out!

François Zaninotto
François ZaninottoAugust 26, 2015
#angular-js#ng-admin#oss

Version 0.8 of ng-admin, the AngularJS admin GUI consuming any RESTful API, is out. Ng-admin 0.8 adds autocompletion on references, a more user-friendly choice list, a new filter interface with permanent filters, a much more powerful dashboard, nested fields, bi-directional mapping, and an improved developer experience. Phew! Let's see all that in detail.

Autocompletion on References

This was one of the oldest feature requests, and a serious limitation for ng-admin. In the creation and edition views, the widget for a relationship (e.g. the widget to set the author for a post) used to fetch the entire list of related entities (e.g. the entire authors list). Clearly this couldn't scale!

The technical solution is well known: autocompletion. To let you set the author for a post, ng-admin now displays a small list of recent authors, plus an input field where you can type a few letters to get the authors matching the search string.

To configure a reference field as an autocomplete widget, use the new remoteComplete() setting:

comment.editionView().fields([
    nga.field('created_at', 'date').label('Posted'),
    nga.field('author.name').label('Author'),
    nga.field('post_id', 'reference')
        .targetEntity(post)
        .targetField(nga.field('title'))
        .remoteComplete(true);
]);

Now when the user types "abc" in the autocomplete field, the choices get populated from the response of a REST API call GET /posts?title=abc.

Unless your API supports fuzzy search on all fields, you'll probably want to force a LIKE comparison. To that extend, you can modify how the search string affects the API query using the searchQuery option:

comment.editionView().fields([
    nga.field('created_at', 'date').label('Posted'),
    nga.field('author.name').label('Author'),
    nga.field('post_id', 'reference')
        .targetEntity(post)
        .targetField(nga.field('title'))
        .remoteComplete(true, {
            searchQuery: function(search) { return { title: '%' + search + '%' }; }
        });
]);

Ui-select For Choices And Reference Fields

Autocompletion was made possible by the switch to ui-select for the dropdown widget and the select multiple widget. Ui-select provides many usability improvements over the previous (native) dropdown widget, including built-in search, multi-select, and a better UI. Fields of type choice, choices, reference, and reference_many now use ui-select in the edition and creation views - you don't need to update your code.

New Filter Interface

Setting more than a couple filters on a list view used to render quite messy. The search experience could be improved, to say the least. Ng-admin 0.8 introduces a new interface for filters - without any change required in the filters configuration.

By default, filters are now hidden behind the "Add filter" button. End users can add them one by one, by choosing the field they want to filter on. That way, an active filter is also much easier to detect.

Filters are applied immediately, without submission, for an instant feedback for the end user. The filters use debounce to avoid DDOSing your REST endpoints each time the end user types a key.

If you need to make a filter always visible, make it pinned(true). And since you can use template fields in filters, it's very straightforward to make a full-text filter with no label, as in the screenshot above:

listView.filters([
  nga
    .field("q", "template")
    .label("")
    .pinned(true)
    .template(
      '<div class="input-group"><input type="text" ng-model="value" placeholder="Search" class="form-control"></input><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span></div>'
    ),
]);

Filters are now much more usable, and nothing prevents you from offering advanced filtering capabilities in all your list views!

Permanent Filters

Sometimes you want a list to be filtered, without letting end users disable this filter. Think "published=true", or "authorId=currentUserId" added to the REST query for a list of posts. The filters() method isn't the right place for this, as its sole purpose is to describe a filter UI. Permanent filters have no UI, they're invisible. That's why ng-admin 0.8 introduces permanentFilters(), a new method available everywhere you may need it. That means that you can use it in the listView, as well as in reference or referenced_list fields.

Usage is straightforward: pass the query parameters to be appended to the REST API endpoint.

// in the listView
posts.listView().permanentFilters({
  published: true,
});
// calls to the API will be GET /posts?published=true

// in a referenced_list
author.editionView().fields([
  nga.field("id"),
  nga.field("name"),
  // display the list of (published) posts for that author
  nga
    .field("post_id", "referenced_list")
    .targetEntity(post)
    .targetReferenceField("author_id")
    .targetFields([nga.field("title")])
    .permanentFilters({ published: true }),
]);
// calls to the API will be GET /posts?author_id=123&published=true

// in a reference (to restrict the options proposed in the dropdown)
post.editionView().fields([
  nga.field("id"),
  nga.field("title"),
  // let the user choose the (approved) author for this post
  nga
    .field("author_id", "reference")
    .targetEntity(author)
    .targetField(nga.field("name"))
    .permanentFilters({ approved: true }),
]);
// calls to the API will be GET /authors?approved=true

Powerful Dashboard

The admin home page is a great place to show recent changes, notifications, or important news. Unfortunately, in previous versions, the only thing that could fit in the ng-admin dashboard were datagrids. That's a pity, especially considering the power of Angular JS and the need for extreme customization of this particular page.

Ng-admin 0.8 introduces a new way to define how the dashboard looks like, decoupling the dashboard data and presentation.

You can setup the dashboard datasources and template on a new member of the admin class, dashboard():

admin.dashboard(
  nga
    .dashboard()
    .addCollection(name, collection)
    .addCollection(name, collection)
    .addCollection(name, collection)
    .template(templateString)
);

The data collections are made available to the dashboard template. The default dashboard template renders all collections as datagrids inside panels - exactly the same as the old dashboard. But since you can use your own HTML/Angular.js template for the dashboard, now the limit is your imagination!

This is the preferred way to customize dashboard panel position, title, and to customize the fields displayed in each panel. Configure a collection just like you would like to configure a listView. For instance:

admin.dashboard(
  nga
    .dashboard()
    .addCollection(
      "posts",
      nga
        .collection(post)
        .title("Recent posts")
        .perPage(5) // limit the panel to the 5 latest posts
        .fields([
          nga
            .field("title")
            .isDetailLink(true)
            .map(truncate),
        ])
        .sortField("id")
        .sortOrder("DESC")
        .order(1)
    )
    .addCollection(
      "comments",
      nga
        .collection(comment)
        .title("Last comments")
        .perPage(5)
        .fields([
          nga.field("id"),
          nga
            .field("body", "wysiwyg")
            .label("Comment")
            .stripTags(true)
            .map(truncate),
          nga
            .field(null, "template")
            .label("")
            .template('<post-link entry="entry"></post-link>'), // you can use custom directives, too
        ])
        .order(2)
    )
    .addCollection(
      "tags",
      nga
        .collection(tag)
        .title("Recent tags")
        .perPage(10)
        .fields([
          nga.field("id"),
          nga.field("name"),
          nga.field("published", "boolean").label("Is published ?"),
        ])
        .order(3)
    )
);

See the Dashboard Configuration dedicated chapter for more details.

Calls to dashboardView() are still supported in ng-admin 0.8, but will raise an error in future versions.

Nested Fields

Another long requested feature! Imagine an API returning nested objects, like the following:

{
    id: 1,
    title: 'War and Peace',
    author: {
        first_name: 'Leo',
        last_name: 'Tolstoi'
    }
}

How to map the inner properties of author to an ng-admin field? With ng-admin 0.8, it's as simple as:

book.listView.fields([
  nga.field("id"),
  nga.field("title"),
  nga.field("author.first_name"),
  nga.field("author.last_name"),
]);

This is so intuitive that developers were surprised that previous versions of ng-admin didn't support it! Now it does, so you don't need ask your API provider to flatten all the fields anymore!

Introducing transform(), the Complement for map()

Many of you already use the Field.map() method a lot. It allows to transform a value from the REST response before it's displayed on the UI. For instance, to display capitalized names for users even though the API format names in all caps, you can use:

users.listView().fields([
  // ...
  nga.field("last_name").map(function capitalize(value, entry) {
    // change 'TOLSTOI' to 'Tolstoi'
    return value.substr(0, 1).toUpperCase() + value.substr(1).toLowerCase();
  }),
]);

The problem comes when you use this technique in an edition or creation view, because when the end user submits the form, it's the mapped value that is sent to the API ('Tolstoi' instead of 'TOLSTOI'). This has unexpected consequences: values are modified even though the end user didn't make such modifications.

That's why ng-admin introduces Field.transform(). This method does the exact opposite as Field.map(): it transforms a value from the UI before it's sent to the REST endpoint. Use it in conjunction with map() to avoid unexpected changes.

users.editionView().fields([
  // ...
  nga
    .field("last_name")
    .map(function capitalize(value, entry) {
      // change 'TOLSTOI' to 'Tolstoi'
      return value.substr(0, 1).toUpperCase() + value.substr(1).toLowerCase();
    })
    .transform(function allCaps(value, entry) {
      // change 'Tolstoi' to 'TOLSTOI'
      return value.toUpperCase();
    }),
]);

If you're using map() and transform() over and over, maybe you should use an ElementTransformer instead.

Improved Developer Experience

Early adopters of ng-admin were very brave, and managed to use the library despite lacking documentation or proper guidance in case of trouble. We've worked a lot to make ng-admin more developer-friendly with this release, and this includes a complete overhaul of the documentation, together with major changes in our tool chain.

First of all, the documentation. We've added a Getting started tutorial, a Frequently Asked Question page, and moved the configuration api reference to a standalone page. The Readme file is now much smaller, and doesn't scare newcomers anymore.

Also, we've switched our build tool to webpack. This makes rebuilds extremely fast when developing, provides sourcemaps for easier debugging, and allows us to get rid of RequireJS, which became a nightmare. In addition, it allows us to write code in ES6 thanks to babel. If you're a contributor, you will find it much easier to understand where to start and how to add a feature.

And lastly, ng-admin is now published both on bower and on npm. Whatever build tool you're using on your side, you will find an appropriate registry to install ng-admin.

Miscellaneous

A large part of the effort for this new release was made in another project, admin-config, which contains the classes used to configure an administration (like Application, Entity, Field, etc). The ng-admin source now only contains the code necessary to render a configured administration to a GUI using angular.js. Moving the (framework agnostic) configuration part out of ng-admin allows us to build other implementations - take a look at react-admin, a React.js implementation of ng-admin.

The float field type makes it easy to display a decimal number. By default, a field of type float shows with three decimals.

If you need to change the HTTP method used for updates (e.g. to use PATCH instead of PUT), ng-admin 0.8 allows you to do so easily:

var post = nga.entity("posts");
post.updateMethod("PATCH");

Conclusion

Ng-admin is more robust that ever, and we currently use it in production for several major projects. We also receive regular feedback from many real-life ng-admin usage from all over the world. With this new release, we've pushed almost 80 individual changes, either new features or bugfixes, as you can see in the Changelog. As always, feel free to give us your feedback by opening an issue in the GitHub tracker!

Did you like this article? Share it!