Admin UI
Custom Components

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:

Nameserviceremarks
fbFormBuilderdefault Angular FormBuilder
routeActivatedRoutedefault Angular ActivatedRoute
routerRouterdefault Angular Router
dialogMatDialogdefault (Angular Material MatDialog)[https://v9.material.angular.io/components/dialog/overview (opens in a new tab)]
snackBarMatSnackBardefault (Angular Material MatSnackBar)[https://v9.material.angular.io/components/snack-bar/overview (opens in a new tab)]
dateAdapterDateAdapterdefault Angular DateAdapter
adminConfigServiceAdminConfigServiceaccess to all client configuration
adminDataServiceAdminDataServicehelper 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:

NameTyperemarks
dataanythe raw data from the api call for this action
viewTypeEntityViewTypethe configuration for this entity/action
parentParentTypeviewType and id of the parent in the current route
idstringthis id (if this is show or edit action
breadcrumbsBreadcrumbsType[]the breadcrumbs for this view (you can edit these if the default doesn't meet for needs)
pathstringthis path property of this viewType
itemsany[]the entity items if this is an index action
itemanythe entity item if this is a create, edit or show action
parentNameanythe parent's entity name of this route (if this is a child route)
parentItemanythe parent's entity item of this route (if this is a child route)
commentsEnabledbooleanwhether this view is configured to show comments
actionCallbacksActionCallbackType[]all configured actions for this view
canNewbooleanwhether the current user is (probably) allowed to call the create mutation
canEditbooleanwhether the current user is (probably) allowed to call the update mutation
canDeletebooleanwhether 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>

ParameterTyperemarks
queryStringOrObjectstring|objectthe 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
variablesobjectany variables you use in your query

mutation( mutationStringOrObject:string|object, variables?:any ):Promise<SaveResult>

ParameterTyperemarks
mutationStringOrObjectstring|objectthe 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
variablesobjectany 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.

ParameterTyperemarks
idstring|undefinedif undefined will call createotherwise update
inputobjectthe input values
filesobjectany file values
entityEntityTypethe entity type

delete( id:string, entity:EntityType ):Promise<string[]>

This calls the delete mutation for an entity.

ParameterTyperemarks
idstringentity item id
entityEntityTypethe entity type