New to Kinvey? Get started today with this tutorial or attend our getting started webinar.

Extension Points

The extension points represent different means by which you can further customize the appearance and behavior of the generated application.

The options for customization include handling of events, extracting common logic in monolithic structures (such as your components and views), tweaking the layout and appearance, importing third-party libraries, and so on.

The available ways to customize the generated application include:

Styling and theming extension points are discussed in App Appearence.

Extending TypeScript Code

The extension points for extending the auto-generated code are represented by specialized files. These files stay intact during app generation to ensure that your custom code is preserved.

The app assets that you can extend include views and components.

To allow you to make changes in a safe and consistent way, Kinvey Studio uses the class extension paradigm. In every custom-code file, you will find one or more classes that extend the auto-generated base classes. The base classes are designed in such a way as to allow maximum extensibility through their public members.

View Level

Kinvey Studio allows you to quickly extend the view code by switching from the Design to the Code tab. However, you will need to know the associated files locations if you prefer to work in another IDE or editor.

All view files are located under the following base path: <app base folder>/src/app/modules/<module name>/<view name>/.

The code extension points under this path include:

  • <view name>.component.<extension>—Extend the base class, access component properties.
  • <view name>.config.ts—(Web views only) A place to register new Angular components on view level.

Where:

  • <app base folder> is the folder where you created the Kinvey Studio application
  • <module name> is the name of the module where you created the view
  • <view name> is the development name (not the display name) of the view
  • <extension> is tns.ts for mobile views and ts for web views

Using the public members of the base class, you can also extend components laid out on the view.

The next example shows a basic even handling scenario that you can add to a web view file like vehicles-web.component.ts. The custom code here is the onRowSelect() function. Don't forget to set the onRowSelect handler name in the view's property inspector, on the Events tab.

import { Inject, Injector } from '@angular/core';
import { VehiclesMobileViewBaseComponent } from '@src/app/modules/main/vehicles-mobile/vehicles-mobile.base.component';

export class VehiclesMobileViewComponent extends VehiclesMobileViewBaseComponent {
    constructor(@Inject(Injector) injector: Injector) {
        super(injector);
    }
    // Handle the grid's onRowSelect event
    onRowSelect(e) {
        console.log(e);
    }
}

If this next example, suppose you want to extend the model of a component with a property that didn't originally exist, but you rather retrieve it from elsewhere, for example the router parameters. Access to the component is accomplished through this.$config inside the onBeforeSubmit event handler.

import { Inject, Injector } from '@angular/core';
import { ApprovedVisitViewBaseComponent } from '@src/app/modules/main/approved-visit/approved-visit.base.component';

export class ApprovedVisitViewComponent extends ApprovedVisitViewBaseComponent {
    constructor(@Inject(Injector) injector: Injector) {
        super(injector);
    }
    // Handle mobile-form’s onBeforeSubmit event
    this.$config.mobileform0.onBeforeSubmit = ({ form }) => {
            form.additionalFields.clientId = this.$activatedRoute.snapshot.queryParams.ClientId;
    }
}

Module Level

On module level, each module that you create offers a couple of extension points on its own, located under <app base folder>/src/app/modules/<module name>/:

  • <module-name>.config.<extension>—A place to register new Angular components, providers, and so on.
  • <module-name>-routing.config.<extension>—Allows you to add custom Angular routes.

Where:

  • <app base folder> is the folder where you created the Kinvey Studio application
  • <module name> is the name of the module
  • <extension> is tns.ts for mobile settings and ts for web settings

Application Level

On application level, the app-routing.config file located under <app base folder>/src/app/ allows you to make application-wide Angular router configuration.

For example, to change the router strategy to hash, use the following code:

import { Routes, ExtraOptions } from '@angular/router';

export const config: { routes: Routes, extraOptions?: ExtraOptions } = {
    routes: [],
    extraOptions: {
        useHash: true
    }
};

export function transformRoutes(routes: Routes): void {}

This next snippet enables router tracing, which makes it easy to track down problems in routing. Tracing output appears in the web browser's or mobile device's console.

import { Routes, ExtraOptions } from '@angular/router';

export const config: { routes: Routes, extraOptions?: ExtraOptions } = {
    routes: [],
    extraOptions: {
        enableTracing: true
    }
};

export function transformRoutes(routes: Routes): void {}

Writing Services

Services are an essential part of your app. This is where you typically do most of the heavy lifting and business logic that feed data to the view controller.

You can write your services as full-fledged Angular services or as utility functions.

As Angular Services

Writing full-fledged services in a Kinvey Studio application does not differ from writing an Angular service.

Place service files in <app base folder>/src/app/core/custom-services/ in .service.ts files. For example, the following IdGeneratorService.service.ts file implements an UUID generator.

import { Injectable } from '@angular/core';

@Injectable()
export class IdGeneratorService {
    constructor() {}

    uuid() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }
}

After implementing the service, you can register it in the core module or in any other module by adding it in the providers array. The following example adds to the core module's files at the following locations:

  • Mobile: <app base folder>/src/app/core/core.config.tns.ts
  • Web: <app base folder>/src/app/core/core.config.ts

You can do the same inside any module's <module-name>.config.tns.ts or <module-name>.config.ts files under <app base folder>/src/app/modules/<module-name>/.

import { NgModule } from '@angular/core';
import { IdGeneratorService } from './id-generator.service';

export const config: NgModule = {
    providers: [
        IdGeneratorService
    ],
    declarations: [],
    imports: [],
    exports: [],
    entryComponents: [],
    bootstrap: [],
    schemas: []
};

export function transformConfig(configMeta: NgModule): void {}

As Utility Functions

Utility functions allow you to write short extensions to your code that don’t logically belong to any class. Place such utilities in <app base folder>/src/app/core/utils.service.ts for web or utils.service.tns.ts for mobile. These files are generated only once at app creation and are not overwritten on app regeneration. They are imported in the core module, meaning you can use your utilities across the web or mobile app respectively.

import { Injectable } from '@angular/core';

@Injectable()
export class UtilsService {

    safeCallFn(context, fnName, args, fallbackFn?: (...args: any) => void) {
        fallbackFn = fallbackFn || (() => { console.error(`${fnName} is not defined!`); });
        return (context[fnName] || fallbackFn).apply(context, args);
    }
}

Overriding Build-in Services

Kinvey Studio implements a number of built-in features as Angular services. You can reimplement a built-in service using a couple of basic steps. The example below reimplements ErrorHandlingService—a service that determines the look and content of error messages.

  1. In <app base folder>/src/app/core/core.config.ts, use the transformConfig function to remove the default ErrorHandlingService from providers and then add a new service to providers.
import { NgModule } from '@angular/core';
import { IdGeneratorService } from './id-generator.service';
import { ErrorHandlingService } from './error-handling.service';
import { CustomErrorHandlingService } from './custom-error-handling.service';

export const config: NgModule = {
    …
};

export function transformConfig(configMeta: NgModule): void {
    configMeta.providers = configMeta.providers.filter(p => p !== ErrorHandlingService);
    configMeta.providers.push({ provide: ErrorHandlingService, useClass: CustomErrorHandlingService });
}
  1. Create a file for the new service (e.g. custom-error-handling.service.ts). In it, extend ErrorHandlingService and add your custom logic.
import { Notification } from '@src/app/core/notification/notification';
import { NotificationService } from '@src/app/core/notification/notification.service';
import { ErrorHandlingService } from './error-handling.service';

@Injectable()
export class CustomErrorHandlingService extends ErrorHandlingService {
    constructor(protected notificationService: NotificationService) {
       super(notificationService);
    }

    protected createErrorNotification(err: any) {
        switch (err) {
            case err.status === 403:
                return new Notification('You are not authorized for that action', 'error', 10000);
            default:
                const message = (err.error && (err.error.message || err.error.error)) || err.message;
                return new Notification(`<ul><li>${message}<li></ul>`, 'error', 10000);
        }
    }
}

Custom Web Component Templates

Kinvey Studio supports templates for implementing custom components. It utilizes the EJS templating engine and expects .ejs files.

In addition to HTML, you can write directives coming from Kendo UI, Kinvey Studio, or third-party components.

The following figure represents the expected tree structure. Add your custom templates in the templates folder at the root application level. The <component_name> placeholder represent the component name.

templates/
└── components/
    └── <component_name>/
        ├── angular/                    -- runtime template
        |   ├── config.json.ejs
        |   └── template.html.ejs
        ├── design-time/                -- design-time template
        |   ├── <component_name>.png
        |   ├── options.json.ejs
        |   └── template.html.ejs
        └── <component_name>.json       -- component schema definition

The component extension points allow you to encapsulate and reuse common logic in the form of a custom component template.

Similar to the built-in components, the custom components can also have properties.

Custom component templates can be beneficial in the following common scenarios:

  • If you need a component that does not exist in the toolbox, you can implement your own or use a third-party component.
  • If you want to augment the functionality or adjust the appearance of a built-in component, you can wrap it in a custom component.

Each custom component template includes the following parts. The examples included in these represent a custom calendar that utilizes built-in Kinvey Studio components.

Schema Definition

The <component_name>.json file represents the schema file that is used by Kinvey Studio to display all available properties and to generate the runtime code. Kinvey Studio utilizes the JSON schema version 4 as a standard.

The name, category, and description properties are optional. The category is used by the toolbox to group the components in a convenient visual manner and accepts any string.

The properties that you define here appear in the properties inspector in Kinvey Studio. Some of the properties descriptor fields are:

  • type—(Required) Renders the proper editor for that property. Possible values and corresponding editors: string (TextBox), boolean (CheckBox), number (Numeric Text Box), integer (Integer Text Box), array (Drop Down List), and object (renders nested, expandable levels in the property grid). Kinvey Studio also supports composite types—for example, [ "integer", "null" ]. Using the correct type provides template users with a hint of what value type is expected.
  • title—(Required) Represents the name of the property that will be displayed in the property grid.
  • description—(Optional) Represents a hint in the code that is also displayed as hint in the property inspector.
  • default—(Optional) Auto-populates the editor too. The generated code will have a default value too.
  • order—(Optional) Defines the order in the property grid for that property.

This properties field description is compliant with the JSON schema version 4. You can also use any other field that is defined in the standard.

After you define properties, you can optionally lay them out using the layout <component_name>.json property. It takes precedence over the properties' order fields.

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "custom-calendar",
    "type": "object",
    "name": "Calendar",
    "description": "Custom Calendar",
    "category": "Scheduling",
    "properties": {
        "id": {
            "allOf": [
                {
                    "$ref": "definitions/valid-component-id.json"
                },
                {
                    "type": "string",
                    "title": "Id",
                    "description": "The Id of the component",
                    "default": "",
                    "minLength": 1
                }
            ],
            "order": 1
        },
        "title": {
            "type": "string",
            "title": "Component Title",
            "description": "The title of the component",
            "default": "Calendar",
            "order": 1
        },
        "minDate": {
            "type": "string",
            "title": "Min Date",
            "default": "1899-12-31T22:00:00.000Z",
            "format": "date-time",
            "editorType": "date",
            "order": 2
        },
        "maxDate": {
            "type": "string",
            "title": "Max Date",
            "default": "2099-12-30T22:00:00.000Z",
            "format": "date-time",
            "editorType": "date",
            "order": 3
        },
        "events": {
            "type": "object",
            "title": "Events",
            "editorRowType": "events",
            "hideTitle": true,
            "order": 6,
            "properties": {
                "onChange": {
                    "type": "string",
                    "title": "Change Event Function",
                    "description": "Fires when the selected date is changed.",
                    "default": ""
                }
            }
        }
    },
    "layout": {
        "groups": [
            {
                "name": "Properties",
                "properties": [
                    { "name": "id" },
                    { "name": "title" },
                    { "name": "minDate" },
                    { "name": "maxDate" }                
                ]
            },
            {
                "name": "Events",
                "properties": [
                    { "name": "events" }
                ]
            }
        ]
    }
}

Design-Time Template

The design-time template defines the component's look and feel when dropped on the canvas in Design mode. The design-time template files are located under ./design-time.

The template consists of several files some of which are optional. However, template.html.ejs is required and represents the template that appears on the Design mode canvas. It can display anything—for example, a Hello World string or a very complex HTML structure, but its purpose is to display sufficiently close visual representation of the generated application version. It is recommended to keep the design-time template simple, as it does not allow for direct interaction. Instead, it reflects any changes you make to its properties using the Property Inspector.

A simple design-time/template.html.ejs could look like this:

<div>Custom Calendar</div>

To add styles to the HTML template, append a <div></div> section at the end of the file and "namespace" the styles with a prefixed class.

<div class="my-custom-calendar">custom calendar</div>

.my-custom-calendar .date-cell {
    color: blue
}

The wrapper HTML element has the my-custom-calendar class with which the .date-cell selector is namespaced. The namespacing class itself has the my- prefix in this case, which is done to minimize the risk of accidental style overrides in the canvas.

  • options.json.ejs—(Required) Defines the template properties that are later used to extend the initial template model. This approach is suitable when you want to provide the template with more dynamic behavior.

    If the template is simple enough, provide an empty object {} so that the file can be JSON validated.

  • generator/index.js—(Optional) Used to augment the initial model of the template and, in this way, provide additional dynamic behavior when designing the application. For example, you can data-bind some components that are inside the view and display sample data. Otherwise, they will be empty and not representative to other developers. Another option for you is to show or hide certain parts of the template based on the selected properties. For example, if the edit property is true, you display a form. In this file you have full access to the meta model. If the template is simple enough, skip it.

  • <component_name>.png—(Optional) Represents the component in the Components pane. If not provided, a default image is displayed.

Runtime Template

The runtime template is an Angular template located under ./angular and consisting of the following files:

  • template.html.ejs—(Required) Represents the Angular component that will be rendered directly in the view when the user adds it. The definition of the component and controller are provided separately. For example, to create a custom calendar component named custom-calendar, the template will look similar to the following example:

      <custom-calendar
         [config]="$config.components.<%- id %>"
         [id]="'<%- id %>'"
         <% if (meta.events.onChange) { %>
         (modelChange)="<%- meta.events.onChange %>($event)"
         <% } %>
         >
      </custom-calendar>
  • config.json.ejs—(Required) Provides a way to pass a subset or calculated set of properties from the meta definition to runtime. The properties become accessible through the $config object as shown above. Under the hood, the generator constructs this object and exposes it as a public member from the base view:

      {
          min: new Date('<%- meta.minDate %>'),
          max: new Date('<%- meta.maxDate %>'),
          title: '<%- meta.title %>'
      }

Component Definition

Kinvey Studio uses the custom component template to generate code and render it inside the application each time the user drags it from the toolbox to the canvas. At this point, Angular does not relate with the rendered <custom-calendar> component and you have to define its template and controller. Otherwise, an exception is thrown.

To define the template and controller:

  1. Create a folder inside <app base folder>/src/app/shared next to the components/ folder and name it, for example, custom-components.
  2. Inside this folder, create a folder for each custom component, and in it create the following files:

    • custom-calendar.component.html:

        <div>
            <h2></h2>
            <kendo-calendar
                #calendar
                    [min]="config.min"
                    [max]="config.max"
                    (valueChange)="valueChange($event)"
                    [formControlName]="id">
            </kendo-calendar>
        </div>
    • custom-calendar.component.ts:

        import { Component, Input, Output, EventEmitter } from '@angular/core';
      
        @Component({
           selector: 'custom-calendar',
           templateUrl: './custom-calendar.component.html'
        })
        export class CustomCalendarComponent {
           @Input() public config;
           @Input() public id;
           @Output() public modelChange: EventEmitter<Date> = new EventEmitter();
      
           _modelChange(e) {
              this.modelChange.emit(e);
           }
        }
    • custom-calendar.component.css—use to write component-specific styles

  3. Handle the calendar event in the parent view:

     public calendarChange(e) {
         console.log(e);
     }

    The name calendarChange comes from the Kinvey Studio property inspector. It can be any valid function name.

    The generated calendar code will look as follows:

     <custom-calendar [config]="$config.components.customcalendar0" [id]="'customcalendar0'" (modelChange)="calendarChange($event)">
     </custom-calendar>

Custom Markup

There are a couple of ways to insert custom markup into your views. One includes using the purpose-built Custom Xml/Html components available for mobile and web views. The other is limited to web views and relies on predefined sections inside the built-in view templates.

The Custom Component

When laying out a view, you can add the Custom Xml (on mobile) or Custom Html (on view) component to inject arbitrary markup. You can even inject Angular components as long as they are part of the application.

After you add the component, click the Edit XML/HTML link in the property inspector to see a text editor where to add the code.

For example, you can use the Bootstrap 4 Alert component in a web view if you have the framework installed. To do that, add the following code to the Custom Html component:

<div class="alert alert-success" role="alert">
  A simple success alert-check it out!
</div>

The result should look like this:

Bootstrap alert

The next example shows how to add a built-in Kinvey Studio component to the Custom XML component. In this case, this is the NativeScript Label component.

<Label text="Example" textWrap="true" class="medium-text"></Label>

Web View Sections

All built-in web views feature predefined sections that you can edit to inject custom HTML code. For example, the Data Grid view features sections called top-section, middle-section, and bottom-section, each of which provides an Edit in Code Mode link that allow you to write your own code to supplement the respective parts of the view.

If you want to edit the section outside of Kinvey Studio, use the following file locations under <app base folder>/src/app/modules/<module name>/<view name>/:

  • Top section: top-section.component.html
  • Middle section: middle-section.component.html
  • Bottom section: bottom-section.component.html

Configuring the Angular Workspace

The angular.json file provides configuration defaults for build and development tools provided by the Angular CLI.

To extend angular.json:

  1. Add an empty angular.json file to the artifacts folder on the root application level.
  2. Add the necessary settings to angular.json.

    The following example changes the root folder of the app:

     {
         "projects": {
             "MyAppName": {
                 "root": "rootFolder"
             }
         }
     }
  3. Restart the development web server if running:

    1. From the toolbar in the upper-right corner, click Stop Dev Server.
    2. After the server stops, click the Start Dev Server button.

Installing NPM Packages

Kinvey Studio enables you to extend the existing package.json file by adding your own packages.

To extend package.json:

  1. Add an empty package.json file to the artifacts folder on the root application level.
  2. Add the necessary dependencies and other changes to package.json.

     {
         "dependencies": {
             "nativescript-push-notifications": "1.1.6"
         },
         "scripts": {
             "hmr": "ng serve --hmr"
         }
     }
  3. Click Generate.

    As a result, the custom package.json file is merged with the automatically-generated one.

Got a question?