# Adding Custom Types
When you map a field between a REST API response and ng-admin, you give it a type. This type determines how the data is displayed and edited. It is very easy to customize existing ng-admin types and add new ones.
## Understanding Types
A ng-admin *type* has two components:
- a Field class, used to configure the field
- a FieldView class, used for rendering
Let's see that through an example: the `number` type.
When you define a field with `nga.field()`, the second argument is the field type ('string' by default). `nga.field()` is a factory method returning a specialized instance of [the `Field` class](https://github.com/marmelab/admin-config/blob/master/lib/Field/Field.js). For instance:
```js
product.listView().fields([
nga.field('price', 'number')
.format('$0.00')
]);
```
The call to `nga.field('price', 'number')` translates to `new NumberField('price')`. The [`NumberField` class source](https://github.com/marmelab/admin-config/blob/master/lib/Field/NumberField.js) is:
```js
import Field from "./Field";
class NumberField extends Field {
constructor(name) {
super(name);
this._type = "number";
this._format = undefined;
}
/**
* Specify format pattern for number to string conversion.
*/
format(value) {
if (!arguments.length) return this._format;
this._format = value;
return this;
}
}
export default NumberField;
```
Yep, this is ES6 syntax, but you get the idea. The sole purpose of a `Field` instance is to store configuration - not data (ng-admin stores data in another object, called the `DataStore`). Here, the `NumberField` can store a rendering format in addition to normal `Field` capabilities.
Once it has fetched data from REST endpoints, ng-admin displays it on screen. What will it be: an image, a text input, an elaborate date widget? This depends on the type, of course, but also on the view. The rendering rules for each type are contained in `FieldView` objects. For instance, the [`NumberFieldView` source](https://github.com/marmelab/ng-admin/blob/master/src/javascripts/ng-admin/Crud/fieldView/NumberFieldView.js) is:
```js
module.exports = {
// displayed in listView and showView
getReadWidget: () => '',
// displayed in listView and showView when isDetailLink is true
getLinkWidget: () => '' + module.exports.getReadWidget() + '',
// displayed in the filter form in the listView
getFilterWidget: () => '',
// displayed in editionView and creationView
getWriteWidget: () => ''
};
```
What this means is that, to render a `NumberField` in a `listView`, ng-admin will use the related `ReadWidget`, which is:
```
```
The `` is a directive defined by ng-admin, but what it essentially does is the following:
```
{{ value() | numeraljs:field().format() }}
```
So a field of type 'number' renders in a `listView` as a formatted number string (e.g. `$45.99`).
One thing that may sound curious is that the configuration logic (`Field` subclasses) is defined in the `admin-config` module, while the rendering logic (`FieldView` subclasses) is defined in the `ng-admin` module. Don't let it confuse you. This is just because the configuration logic can be reused by another renderer not based on Angular.js (e.g. [react-admin](https://github.com/marmelab/react-admin)).
One last thing to understand is that ng-admin uses the field type *name* (e.g. 'number') to relate a Field subclass with a FieldView subclass. For type 'number', the [registered Field subclass is `NumberField`](https://github.com/marmelab/admin-config/blob/master/lib/Factory.js#L116), and [the registered FieldView subclass is `NumberFieldView`](https://github.com/marmelab/ng-admin/blob/master/src/javascripts/ng-admin/Crud/config/factories.js#L11). You'll see shortly how to register your own classes.
## Writing a Custom Field Class
Let's write an `AmountField` to manage not only numbers, but amounts. An amount, in addition to a number, has a currency. Create the following `AmountType.js` class in your project tree:
```js
import NumberField from 'admin-config/lib/Field/NumberField';
class AmountField extends NumberField {
constructor(name) {
super(name);
this._type = 'amount';
this._currency = '$';
}
currency(currency) {
if (!arguments.length) return this._currency;
this._currency = currency;
return this;
}
}
export default AmountField;
```
Compared to a `NumberField`, this adds a `.currency()` method, which is both a getter and a setter. The `AmountField` code is ES6, so you'll need to transpile it to JavaScript to make it executable by a web browser. The solution depends on your build tool ; here is the configuration for [Webpack](http://webpack.github.io/) and [babel](https://babeljs.io/).
```js
// in webpack.config.js
module.exports = {
// ...
module: {
loaders: [
{ test: /\.js/, loaders: ['babel'], exclude: /node_modules/ },
]
}
};
```
The `AmountField` class depends on the `NumberField` class from the `admin-config` module. You'll have to install it.
```sh
npm install admin-config --save-dev
```
This module is also written in ES6, so update the `exclude` pattern in `webpack.config.js` to let Webpack transpile all "*.js" file, except the ones under `node_modules`, but including `node_modules/admin-config`:
```js
// in webpack.config.js
module.exports = {
// ...
module: {
loaders: [
{ test: /\.js/, loaders: ['babel'], exclude: /node_modules\/(?!admin-config)/ }
]
}
};
```
You need to *register* this new field type in your application:
```js
myApp.config(['NgAdminConfigurationProvider', function(nga) {
nga.registerFieldType('amount', require('path/to/AmountField'))
}]);
```
Now you can use the new type in your admin configuration:
```js
product.listView().fields([
nga.field('price', 'amount')
.format('0.00')
.currency('$')
]);
```
## Defining The Rendering Logic For a Type
An `amount` field will render as text with the currency in read context, and as an input with a lateral addon in write context. Create the following `AmountFieldView` to define the rendering logic:
```js
export default {
getReadWidget: () => '{{ field.currency() }}',
getLinkWidget: () => '' + module.exports.getReadWidget() + '',
getFilterWidget: () => '',
getWriteWidget: () => '{{ field.currency() }}
'
};
```
You also need to *register* this new field view type in your application for ng-admin to find it. Beware that it's a different configuration provider this time:
```js
myApp.config(['FieldViewConfigurationProvider', function(fvp) {
fvp.registerFieldView('amount', require('path/to/AmountFieldView'))
}]);
```
## Using Custom Directives
Optionally, you can write a custom directive, and use that directive in one of the widget definitions.
```js
function amountStringDirective() {
return {
restrict: 'E',
scope: {
value: '&',
field: '&'
},
template: '{{ field().currency() }}{{ value() | numeraljs:field().format() }}'
};
}
export default amountStringDirective;
```
```js
myApp.directive('amountString', require('path/to/amountStringDirective.js'));
```
```js
// in AmountFieldView.js
export default {
getReadWidget: () => '',
// ...
};
```
## Fetching data
Custom directives can fetch data, too. For instance, the `` directive can fetch the conversion rate to another currency, and display the amount in more than one currency. To fetch a remote API, use either the `Restangular` service, or `$http` if you prefer.
```js
function amountStringDirective($http) {
return {
restrict: 'E',
scope: {
value: '&',
field: '&'
},
link: function(scope) {
scope.fromCurrency = field().currency();
scope.conversionRate = 1;
scope.format = field().format();
$http.get(`http://myconverter.example.com?fromCurrency=${scope.fromCurrency}&toCurrency=USD`)
.then(response => {
scope.conversionRate = response.value;
});
},
template: `{{ fromCurrency }} {{ value() | numeraljs:format }}
( USD {{ value() * conversionRate | numeraljs:format }})`
};
}
amountStringDirective.$inject = ['$http'];
export default amountStringDirective;
```
## Fetching Data Before Rendering
Fetching data from the `link()` function of a directive has drawbacks:
- it triggers redraws of the page when the remote response arrives
- in a `listView`, a custom directive will make a lot of queries (one per entry)
The alternative is to prepare the data before the page starts rendering, to store it in the `datastore`, and to pass the `datastore` to the custom directive.
Fortunately, every view has a `prepare()` method, which expects a function as parameter. This function is executed before the view is rendered. It's the ideal place to group fetches to a remote API, and store the results in the `datastore`. The `prepare()` function uses the Angular Dependency Injection system, so you can require any service defined previously in the controller logic - like the current DataStore instance (already filled with all the entries required by the view).
```js
product.listView().prepare(['$http', 'datastore', 'view', function($http, datastore, view) {
const fromCurrency = view.getField('price').currency();
return $http.get(`http://myconverter.example.com?fromCurrency=${fromCurrency}&toCurrency=USD`)
.then(response => {
datastore.addEntry('conversionRate', response.value)
});
}])
```
The directives doesn't need `$http` anymore, but can use the `datastore` instead:
```js
function amountStringDirective($http) {
return {
restrict: 'E',
scope: {
value: '&',
field: '&',
datastore: '&'
},
link: function(scope) {
scope.fromCurrency = field().currency();
scope.conversionRate = 1;
scope.format = field().format();
scope.convertedValue = datastore.getFirstEntry('conversionRate') * scope.value();
},
template: `{{ fromCurrency }} {{ value() | numeraljs:format }}
( USD {{ convertedValue | numeraljs:format }})`
};
}
export default amountStringDirective;
```
The `datastore` just needs to be declared in the `fieldView`:
```js
// in AmountFieldView.js
export default {
getReadWidget: () => '',
// ...
};
```
## Overriding Existing Types
Just like you can add new types, it's very easy to override existing types. For instance, if you want all `number` fields to use a directive of yours called ``, create a `CustomNumberFieldView` script as follows:
```js
module.exports = {
getReadWidget: () => '',
// ...
};
```
Then, register this field view for the `number` type:
```js
myApp.config(['FieldViewConfigurationProvider', function(fvp) {
fvp.registerFieldView('number', require('path/to/CustomNumberFieldView'))
}]);
```
## Conclusion
Once you get passed the installation step, defining new types is straightforward. It's also a very powerful way to extend ng-admin to your custom needs. Use it as much as possible!