The marmelab blog

Ng-admin 0.5 Is Out, and It's Compatible With Your API

Published on 4 February 2015 by François with tags ng-admin angular-js oss

We are very happy to announce the new 0.5 release of ng-admin, the AngularJS admin GUI consuming any RESTful API. For this major release, which introduces a few BC breaks, we’ve focused on allowing ng-admin to fit with existing systems. You won’t need to adapt your RESTful API for ng-admin: ng-admin will adapt to your RESTful API. There’s a ton of new features, too, all resulting from real-life use cases. And performance was improved, too. Read on to get a detailed run through the updates.

API Mapping

REST is a very lightweight standard. That leaves many decisions to make regarding the undocumented areas for server implementations. How should a RESTful API expose the number of rows in a collection? RFC7233 specifies it should be in a Content-Range header:

GET /api/players HTTP1.1

HTTP/1.1 200 OK
Content-Range: entities 21-40/66
Content-Type: application/json
[
  { "id": 235, "login": "John", "level": 8 },
  { "id": 863, "login": "Jane", "level": 12 },
  ...
]

But RFC7233 isn’t part of any official REST standard, and I bet 90% of the APIs you’ll connect to expose content range in a different way.

Thats is why ng-admin now allows you to completely override the mapping between your RESTful APIs and the internal resource model. This concerns request and response formatting, query parameters and headers, body format, field naming… everything. Ng-admin relies on the interceptor capabilities of the Restangular library, which is used under the hood to fetch resources over HTTP.

As an example, when fetching a single resource, ng-admin reads the full response body by default:

GET /api/players/235 HTTP1.1

HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 235, "login": "John", "level": 8 }

You can configure ng-admin 0.5 to understand responses with more complex structures, as in the following example:

GET /api/players/235 HTTP1.1

HTTP/1.1 200 OK
Content-Type: application/json
{
  "status": 567,
  "entity": { "id": 235, "login": "John", "level": 8 }
}

All it takes is an Element Transformer in the application configuration:

app.config(function(RestangularProvider) {
    RestangularProvider.addElementTransformer('players', function(element) {
        return element.entity;
    });
});

The ng-admin documentation offers a full chapter dedicated to API mapping, with many examples. You’ll see that there is no need to adapt your APIs to ng-admin anymore - just do it the other way around.

Filters

Perhaps the #1 requested feature by early adopters, filters on the list view are now fully functional.

If your API supports filters, ng-admin will take advantage if it. The configuration used to define the filters above is:

comment.listView()
    .filters([
        new Field('q').type('string').label('').attributes({'placeholder': 'Global Search'}),
        new Field('created_at').type('date').label('Posted').attributes({'placeholder': 'Filter by date'}).format('yyyy-MM-dd'),
        new Field('today').type('boolean').map(createdToday),
        new Reference('post_id').label('Post').targetEntity(post).targetField(new Field('title'))    
    ]);
function createdToday() {
    var now = new Date(),
        year = now.getFullYear(),
        month = now.getMonth() + 1,
        day = now.getDate();
    month = month < 10 ? '0' + month : month;
    day = day < 10 ? '0' + day : day;
    return {
        created_at: [year, month, day].join('-') // ?created_at=... will be appended to the API call
    };                    
}

In particular, the ability to filter by a set of dynamic parameters with a single click, previously known as “quick filters”, is now simply a boolean filter. Boolean filters can be combined with other filters and use the same syntax.

Refer to the listView documentation for more details about adding and customizing filters on the list views.

Multi-Backend

We’ve already mentioned it in a previous blog post dedicated to the multi-backend abilities of ng-admin, so no need to review this new feature extensively again. Just remember that ng-admin can now plug to APIs developed in Symfony, Node.js, Django, Ruby on Rails… all at the same time. It’s THE solution for microservices administration with a unified UI.

Theming Support

Configurable templates! CSS classes everywhere! Menu icons! Even the errors are configurable! With ng-admin 0.5, your creativity is no longer constrained by the choices we made for the look and feel.

Do you need to add a login form in the main layout? You’re covered. Display filters as a right hand-side column instead of a horizontal bar? Check. Adapt the show view layout for a particular entity? Ditto.

Head to the Theming documentation to discover how to customize the default GUI to your preferences.

And this is probably a good occasion to explain a choice we’ve made about the default look and feel. The first time you look at ng-admin, you probably find it harsh, dimmed out, not very original:

That’s on purpose. We consider that users of an admin GUI should be able to focus on the content, not the chrome. So we’ve made the chrome as discreet as possible, still following the good practices of usability and design. Feedback from our early adopters is surprisingly good: they don’t want any more fancy fonts or flashy colors. ng-admin lets them work efficiently with their content.

JSON Field And Schemaless Entities

Some of the RESTful endpoints we connect to expose data from NoSQL databases. We needed a way to let our users change the schemaless values without spending too much time writing a custom directive. So here it is: the JSON field, complete with syntax highlighting in the editionView and table formatting in the show view.

All it takes is a type('json') in your configuration:

post.editionView()
    .fields([
        ...
        new Field('pictures').type('json'),
        ...
    ]);

However, this solution is for developers only: don’t let non-tech people edit JSON by hand, even though the built-in validation refuses non-valid JSON strings! If a given JSON field has a fixed schema, you’ll probably prefer to replace the textarea by a custom set of inputs. Thanks to the template type and the power of Angular databinding, you can do that very easily using a custom directive.

The json field isn’t the only new field type added to 0.5. There is also the file type, which allows to upload new files to the backend.

Performance and Robustness

Our developers love AngularJS for the speed of development it offers. Adding a complex feature, backed up by unit tests, often takes only a couple hours. But it’s also very easy to introduce memory leaks in AngularJS applications. A large part of the refactoring done in 0.5 aims to better isolate each component, reducing memory consumption and allowing for painless garbage collection.

Among others, we’ve fixed the infamous n+1 request problem in the list view. You’ve probably met this case before: you display a list of comments, each comment related to a single post. You want to display the title of the related post in addition to the comments detail.

In ng-admin, it’s as easy as defining a Reference field:

comment.listView()
    .fields([
        new Field('created_at').label('Posted').type('date'),
        new Field('author'),
        new Field('body').map(truncate).order(3),
        new Reference('post_id')
            .label('Post')
            .targetEntity(post)
            .targetField(new Field('title')),
    ]);

This works fine, but the ng-admin client makes one HTTP request per line to fetch the related post entities. Of course, there is an instance pool system to avoid fetching twice the same entity, but still, this makes potentially a lot of requests to the API backend:

GET /comments?_end=10&_sort=id&_sortDir=DESC&_start=0
GET /posts/1
GET /posts/2
GET /posts/3
GET /posts/5
GET /posts/6
GET /posts/9

In ng-admin 0.5, you set a singleAPICall() function to transform these n calls into a single one:

comment.listView()
    .fields([
        // ...
        new Reference('post_id')
            .label('Post')
            .targetEntity(post)
            .targetField(new Field('title'))
            .singleApiCall(function (postIds) {
                return {
                    'post_id[]': postIds
                };
            })
    ]);

And now, the same list pages requires only 2 HTTP queries to your API:

GET /comments?_end=10&_sort=id&_sortDir=DESC&_start=0
GET /posts?_end=30&_sort=id&_sortDir=DESC&_start=0&post_id%5B%5D=1&post_id%5B%5D=2&post_id%5B%5D=3&post_id%5B%5D=5&post_id%5B%5D=6&post_id%5B%5D=9

The query parameter of the second call, once URL decoded, looks like:

_end=30&_sort=id&_sortDir=DESC&_start=0&post_id[]=1&post_id[]=2&post_id[]=3&post_id[]=5&post_id[]=6&post_id[]=9

It’s up to your API to support such a multi-id query, but it’s usually easier to implement than complex filtering capabilities.

We’ve made even more optimizations. We have been profiling our applications running ng-admin, fixed leaks, streamlined the markup, improved the overall performance, and we’re now confident that ng-admin can handle your data in an efficient way.

Developer-Friendly

We develop ng-admin for 2 types of users: end users, who want a slick web UI and a clear view over their data, and developers, who build the web UI with what we call the Configuration API - using listView() and new Field() and Reference and all that.

In ng-admin 0.5, we have made many adjustments to the Configuration API to make it more usable to developers. For instance, instead of calling addField() repeatedly, you can just call fields() with an array of fields:

// ng-admin 0.4
post.listView()
    .addField(new Field('id'))
    .addField(new Field('title'))
    .addField(new Field('published_at').type('date'))
    .addField(new Field('views').type('number'))
    .addField(new ReferenceMany('tags')
            .targetEntity(tag)
            .targetField(new Field('name'))
    )
// ng-admin 0.5
post.listView()
    .fields([
        new Field('id'),
        new Field('title'),
        new Field('published_at').type('date'),
        new Field('views').type('number'),
        new ReferenceMany('tags')
            .targetEntity(tag)
            .targetField(new Field('name'))
    ])

Moreover, fields() is like other smart getters/setters in ng-admin. Called without arguments, it returns the current value. This allows you to reuse fields across views:

post.showView()
    .fields(post.listView().fields());

To further improve the developer experience, we have doubled the amount of documentation. For instance, how can you display a custom screen in the ng-admin layout? Just read the doc and you’ll know. Learning by example is also very efficient, so we have expanded the “blog admin” example bundled with the source.

The Path To 1.0

This release is still not considered worthy of a 1.0 tag. That’s because ng-admin, in its current state, misses a few important features to be considered truly ready for prime time. These include:

  • Autocomplete widget for relations (to allow references to large collections)
  • Role-based authorization (to let you grant access to the admin on a per-entity basis)
  • CSV import/export
  • Datetime widget
  • Adjustments to the Configuration API (we’re not entirely satisfied with the clarity and speed of development here)

Nevertheless, ng-admin 0.5 is already used in production, so if your admin backend does’nt need the above features right now, you can use it. It’s more stable and faster than ever!

The ng-admin Community

The ng-admin project is heavily sponsored by marmelab, but we’ve already seen many new developers joining the effort. GitHub counts 17 contributors already, and we’re very proud that so many GitHub users granted us a star.

So if you wonder which tool you should use for your next admin, don’t hesitate anymore: choose ng-admin!

comments powered by Disqus