Marmelab Blog

ng-admin 0.9: embedded lists, templates everywhere, new documentation

Each new ng-admin release brings a lot of new features. Each new release aims to help developers build more sophisticated admin interfaces, faster. Version 0.9 is no exception - it's probably the most ambitious release ever. Read on to see what's new.

Support For Embedded Lists

By far the most popular feature request for the past couple of months was the ability to map nested arrays in entities. For instance, if the REST API behaves as follows:

GET /posts/1
{
    "id": "1",
    "title": "Consectetur adipisicing elit",
    "body": "Sed do eiusmod...",
    "comments": [
        {
            "author": "Alice",
            "body": "Lorem ipsum sic dolor amet...",
        },
        {
            "author": "Bob",
            "body": "Lorem ipsum sic dolor amet...",
        }
    ]
}

This is usually a representation of a one-to-many relationship, by embedding the related entities in the main response.

Version 0.9 introduces a new field type called embedded_list. Mapping the comments property of the post entity to an embedded_list will tell ng-admin to use the embedded comment entities.

post.showView().fields([
    nga.field('comments', 'embedded_list') // Define a 1-N relationship with the (embedded) comment entity
        .targetFields([ // which comment fields to display in the datagrid / form
            nga.field('body')
        ])
]);

Ng-admin renders embedded_list fields as a datagrid in read context (showView):

In write context (creationView and editionView), ng-admin renders embedded_list fields as a list of embedded forms.

Although very convenient for developers, we've found that these embedded forms often lead to poor usability. Each time we've had to put is in front of an end user, we ended up writing a custom directive to make the embedded data easier to edit (see for instance the shopping cart directive in our demo). So our advice is: don't abuse of embedded_list fields in write context.

Displaying an embedded_list fields, whether in read or in write context, won't issue any additional query to the REST API, since the related entities are already embedded. These fields support custom sorting and filtering, and of course you can use the usual field types to map the properties of the nested entities.

This brings the number of field types dedicated to relationships to 4 : reference, referenced_list, reference_many, and referenced_list. Do you feel lost? We've added a new Relationships chapter to the documentation to clarify the use case for each of these.

All Fields Can Now Have A Custom Template

What used to be a privilege of the template field type (a custom template) is now available to all field types. The template() method makes it easy to customize the look and feel of a particular field without sacrificing the built-in features.

For instance, if you want to customize the appearance of a NumberField according to its value:

listview.fields([
    nga.field('amount', 'number')
        .format('$0,000.00')
        .template('<span ng-class="{ \'red\': value < 0 }"><ma-number-column field="::field" value="::entry.values[field.name()]"></ma-number-column></span>')
]);

The template scope exposes the following variables:

  • value, field, entry, entity, and datastore in listView and showView
  • value, field, values, and datastore in filters
  • value, field, entry, entity, form, and datastore in editionView and creationView

Most of the time, template() is used to customize the existing ng-admin directives (like <ma-number-column> in the previous example), for instance by decorating them. If you want to learn about these native directives, explore the column/, field/, and fieldView/ directories in ng-admin source.

Since every field can now have a custom template, there is no need for a specialized template field type anymore. Existing configurations using this type will continue to work, but you can safely use templates with the default field type.

If the example above is easy to understand, as a matter of fact it's not a good use case for a custom field template. You can already add a conditional CSS class by passing a function to the cssClasses() method. But you get the idea!

listview.fields([
    nga.field('amount', 'number')
        .format('$0,000.00')
        .cssClasses(function(entry) { return entry.values.amount < 0 ? 'red' : '' });
]);

Easier Custom types

The addition of the .template() method to all field types reduces the need for custom types. But there are still situations where a custom template isn't enough, and where you need to store different settings in a Field instance in addition to rendering the field in a special way.

Writing custom types has been possible in ng-admin since version 0.6, but to be honest it was really tricky. It used to require an ES6 workflow, and knowledge of ng-admin internals. That's not the case anymore. Custom types are now better documented, and easy to use even in an ES5 workflow:

var DateField = require('admin-config/lib/Field/DateField');
function MyCustomDateField(name) {
    DateField.call(this, name);
}
MyCustomDateField.prototype = new DateField();
MyCustomDateField.prototype.formatSmall = function() {
    return this.format('small');
}
module.exports = MyCustomDateField;

Since ng-admin is now written in ES6, the idiomatic way is to use import and class instead:

import DateField from 'admin-config/lib/Field/DateField';
export default class MyCustomDateField extends DateField {
    formatSmall() {
        return this.format('small'):
    }
}

Default Filter Value

When designing filters for our own entity lists, we needed to be able to add a filter with a default value. For instance, a "Published since" filter would default to two weeks ago, and an "Author" filter would default to the current editor username, to make filters faster to use. Since filters use Field instances internally, it was just a matter of plugging the defaultValue() method to the filter form:

post.listView().filters([
    nga.field('published_at_gte', 'datetime')
        .label('Published since')
        .defaultValue(new Date(Date.now() - 2 * 7 * 24 * 60 * 60 * 1000)),
    nga.field('author', 'reference')
        .targetEntity(nga.entity('user'))
        .targetField('username')
        .defaultValue(window.currentUserId),
])

This looks like a small detail, but it opens the posibility to have non editable filters - a.k.a. quick filters. A quick filter is a filter that users add, without having to give it a value to have it applied. For instance, a "Published" filter:

post.listView().filters([
    nga.field('published')
        .defaultValue(true)
        .template(' '),
])

You can see an example of such quick filters in the Posters Galore demo: "Low stock" is a filter with a default value that the user doesn't need to worry about.

Boolean Fields Now Render Either As a Dropdown, or as a Checkbox

A field of type boolean can have 3 values: true, false, or undefined. That's why the form widget for boolean fields is a dropdown and not a checkbox.

editionView.fields([
    nga.fields('published', 'boolean')
]);

This isn't true, of course, for boolean fields which require a non-null value. In that case, they still render as a checkbox.

editionView.fields([
    nga.fields('published', 'boolean')
        .validation({ required: true })
]);

For the dropdown version, you can override the default labels for the 3 values using the choices() method:

editionView.fields([
    nga.fields('published', 'boolean')
        .choices([
            { value: null, label: 'not yet decided' },
            { value: true, label: 'enabled' },
            { value: false, label: 'disabled' }
        ])
]);

Default Values For creationView

<ma-create-button> now supports default values for the entity to create. This enables the creation of an entity with a prefilled relationship:

post.editionView()
    .fields([
        ...
        nga.field('').label('')
            .template('<ma-create-button entity-name="comments" size="sm" label="Create related comment" default-values="{ post_id: entry.values.id }"></ma-create-button></span>')
    ]);

Accordion menus are very common these days, but users are still divided in two camps regarding their usage. On one side, users who want a section to close when they select another section. On the other side, users who REALLY DON'T WANT a section to close on its own - they like to be in charge. Our initial implementation of second-level menus satisfied only the first camp. We've heard loud voices from the second camp. Today, you can choose:

nga.menu().autoClose(false); // true by default

Rewritten Reference Documentation and Examples

This is perhaps where most of the work for 0.9 took place. With better documentation and better examples, developers will develop administration backends faster.

The documentation is now published to a standalone domain, ng-admin-book.marmelab.com, courtesy of GitBook. It is searchable, has a table of contents, and is better-looking than before. You can also read it offline (pdf and eBook formats are available at https://www.gitbook.com/book/marmelab/ng-admin/details). The Reference chapter has been rewritten and split. You will find information much more easily, and there should not be too many loopholes. And if you find one, just click on the "Edit this page" link on top of every documentation page. As we keep our documentation in GitHub (in the main ng-admin repository), fixing typos is a just matter of opening a Pull Request.

We've already mentioned it in a previous post: ng-admin has a new demo site called "Posters Galore". It's much more sophisticated than the Blog demo, and is a great showcase for ng-admin's features. If you need a feature that you found on Posters Galore, read the source! It's built in ES6, compiled with WebPack, and follows all the best practices of ng-admin development.

Conclusion

That's a lot of great new features, don't you think? Not to mention all the bugs we've fixed since version 0.8. Upgrade is painless - no BC break. Check the Changelog for the extensive list of changes. As always, feel free to give us your feedback by opening an issue in the GitHub tracker.

We're very glad to push ng-admin even further. We're very glad of its success, too! With more than 2,000 stars on GitHub, regular feedbacks from users, and an active community (check the ng-admin channel on Gitter to get a glimpse), ng-admin goes way beyond our personal backend generator. It solves many real-world problems, for many developers around the world. If you use it, and if you like it, please mention ng-admin to you friends!