Custom Components
The AdminUI offers you generic though configurable views for the default actions of an entity. While this might be sufficient for simple use cases, or as starter for your own development, it often doesn't give you the freedom you need to implement your desired behavior.
To achieve this you can simply replace any view for a certain entity/action with you own implementation. Thus allowing you to still benefit from all the convention over configuration "magic" but giving you full freedom to implement anything you see fit on your own.
Let's say you're not happy with the default show view for your Car entity. So we add a component and route to your application (as any regular Angular component).
Component Class
import { Component } from '@angular/core';
import { ShowComponent } from 'activeql-admin-ui';
@Component({
selector: 'app-car',
templateUrl: './car.component.html',
styleUrls: ['./car.component.scss']
})
export class CarComponent extends ShowComponent {
}As you see, you subclass the ShowComponent and therefore can use any services and configuration as with the generic view. Make sure not to override ngOnInit
since the inherited view component implements most of its data handling there. You can safely override prepareComponent which is called once all data are
handled.
Component Route and Resolver
This data handling is done by a Resolver so lets see how to configure the route for this component in your Angular application's route.
const routes:Routes = [
{ path: '', pathMatch: 'full', redirectTo: '/welcome' },
{ path: 'login', pathMatch: 'full', component: LoginComponent },
{ path: 'welcome', pathMatch: 'full', component: WelcomeComponent },
{ path: 'admin', loadChildren: 'AdminRoutingModuleWrapper' },
{ path: 'my', children: [
{
path: 'car/:id',
pathMatch: 'full',
component: CarComponent,
resolve: { data: AdminDataResolver },
runGuardsAndResolvers: 'always',
data: { path: 'cars', action: 'show' }
},
}
]In your adminConfig you can now replace the generic route with this route:
export const config:AdminConfig = {
routes: {
Car: {
show: '/my/car/:id',
}
}
}You can still use the default route /admin/cars/show/:id - it will just be replaced with the new route /my/car/:id from now on.
Since we want to use the AdminDataResolver we have to configure this in the routes configuration and we have to make sure the resolver knows
which path, parent, parentId, action and id to use. Since we currently only use id as route param and we need
to know path an action we can put this in the route's data. So the resolver will resolve as if would see the route we're replacing.
Component View
While the component class now behaves exactly as the generic component we cannot inherit a view. The suggested way is to copy the content from
/angular/projects/activeql-admin-ui/src/lib/components/show/show.component.html to your component's html file. Now you should be able to use everything as
before, but now you can add or edit anything in your car's view.
Implement custom requirements
By following the above steps you now have your own Component class and view and route that behave exactly as the default. A great start to now add your custom requirements. Keep in mind this the recommended way but you can of course use any component and implement anything on your own. Using an inherited component makes sure the default behavior (in this example querying the API for a car item, rendering it and links to other actions ) and gives you access to the following services:
| Name | service | remarks |
|---|---|---|
| fb | FormBuilder | default Angular FormBuilder |
| route | ActivatedRoute | default Angular ActivatedRoute |
| router | Router | default Angular Router |
| dialog | MatDialog | default (Angular Material MatDialog)[https://v9.material.angular.io/components/dialog/overview (opens in a new tab)] |
| snackBar | MatSnackBar | default (Angular Material MatSnackBar)[https://v9.material.angular.io/components/snack-bar/overview (opens in a new tab)] |
| dateAdapter | DateAdapter | default Angular DateAdapter |
| adminConfigService | AdminConfigService | access to all client configuration |
| adminDataService | AdminDataService | helper service to execute queries and mutation on the ActiveQL API |
Let's say you have implemented a query nextInspection on your API that expects a carId and would somehow calculate the next inspection date for car.
You want to display this information on the car's detail view component we just created.
import { Component } from '@angular/core';
import { ShowComponent } from 'activeql-admin-ui';
@Component({
selector: 'app-car',
templateUrl: './car.component.html',
styleUrls: ['./car.component.scss']
})
export class CarComponent extends ShowComponent {
nextInspection:string;
async prepareComponent() {
const query = 'query{ nextInspection(carId: "${this.id}") }';
const data = await this.adminDataService.query( query );
this.nextInspection = data['nextInspection'];
}
}Now every time after loading a car item this query is executed and you can use the value in nextInspection to display the information. Of course you can
implement this in any other way (e.g. after the user clicked a button). But that is standard Angular development and will not be covered here.
See how we can safely use this.id because the super class did all the default data handling for us.
You can use the following instance variables in your subclasses:
| Name | Type | remarks |
|---|---|---|
| data | any | the raw data from the api call for this action |
| viewType | EntityViewType | the configuration for this entity/action |
| parent | ParentType | viewType and id of the parent in the current route |
| id | string | this id (if this is show or edit action |
| breadcrumbs | BreadcrumbsType[] | the breadcrumbs for this view (you can edit these if the default doesn't meet for needs) |
| path | string | this path property of this viewType |
| items | any[] | the entity items if this is an index action |
| item | any | the entity item if this is a create, edit or show action |
| parentName | any | the parent's entity name of this route (if this is a child route) |
| parentItem | any | the parent's entity item of this route (if this is a child route) |
| commentsEnabled | boolean | whether this view is configured to show comments |
| actionCallbacks | ActionCallbackType[] | all configured actions for this view |
| canNew | boolean | whether the current user is (probably) allowed to call the create mutation |
| canEdit | boolean | whether the current user is (probably) allowed to call the update mutation |
| canDelete | boolean | whether the current user is (probably) allowed to call the delete mutation |
Please be that if a canXxx property is true it is not guaranteed a user is actually allowed to call a mutation, since this could be a complex implementation on the server. On the other hand a false means definitely this user is not allowed to call this mutation.
AdminDataService
While you can certainly implement any API call as you like the AdminDataService comes handy when you want to execute queries and mutations on your ActiveQL API.
It offers you the following methods
query( queryStringOrObject:string | object, variables?:any ):Promise<any>
| Parameter | Type | remarks |
|---|---|---|
| queryStringOrObject | string|object | the query to execute on the ActiveQL API. This can either be a string or a json-to-graphql-query (opens in a new tab) object |
| variables | object | any variables you use in your query |
mutation( mutationStringOrObject:string|object, variables?:any ):Promise<SaveResult>
| Parameter | Type | remarks |
|---|---|---|
| mutationStringOrObject | string|object | the mutation to execute on the ActiveQL API. This can either be a string or a json-to-graphql-query (opens in a new tab) object |
| variables | object | any variables you use in your mutation |
save( id:string|undefined, input:any, files:_.Dictionary<File>, entity:EntityType ):Promise<SaveResult>
This calls the create or update mutation for an entity.
| Parameter | Type | remarks |
|---|---|---|
| id | string|undefined | if undefined will call createotherwise update |
| input | object | the input values |
| files | object | any file values |
| entity | EntityType | the entity type |
delete( id:string, entity:EntityType ):Promise<string[]>
This calls the delete mutation for an entity.
| Parameter | Type | remarks |
|---|---|---|
| id | string | entity item id |
| entity | EntityType | the entity type |