🚀 Welcome to MDriven Learn –  MDriven is now on Discord!  Don’t miss the latest Release Notes.
EXT Components
This page was created by Hans.karlsen on 2017-07-15. Last edited by Wikiadmin on 2026-07-29.

You can replace the AngularJS rendering of a ViewModel column in a Turnkey web application by creating an EXT_Components component and assigning it to that column; this page is for developers customizing the AngularJS client.

This page applies to AngularJS components. For Razor and WebAssembly components in the Blazor client, use Documentation:EXT ComponentsBlazor.

What an EXT component does

An EXT component is a folder of HTML, CSS, and optional JavaScript that Turnkey uses to render a selected ViewModel column. The folder name is the component name. You assign that name with the Angular_Ext_Component tagged value on the column that the component should render.

Use an EXT component when the standard Turnkey rendering does not meet your needs. For example, you can:

  • Replace a text editor with custom markup.
  • Render a collection as a read-only table.
  • Load a client-side library and connect it to data already available in the ViewModel.
  • Add an AngularJS directive that creates or manages browser-side content.

For the general role of components in MDriven, see Documentation:Component.

Component folder structure

In the Turnkey web application, locate the EXT_Components folder. Create one subfolder for each component. The subfolder name is the name you use in the Angular_Ext_Component tagged value.

For a component named test1, use this structure:

EXT_Components/
  test1/
    test1.cshtml
    test1.css
    test1_module.js
File Required Purpose and loading behavior
<component name>.cshtml Yes Defines the HTML structure that replaces the standard rendering. For test1, the file must be test1.cshtml.
CSS files No All CSS files in the component folder are loaded. When both <name>.css and <name>.min.css exist, Turnkey uses the minified file and skips the non-minified file.
JavaScript files whose filename contains _module No Loaded when the Angular application starts. Turnkey adds type='module' to the script tag.

Keep files for one component in its own folder. For example, a component named plaintable has a plaintable folder containing plaintable.cshtml and its styles. See Documentation:A simple table component for just listing a collection for a complete reusable collection-table example.

Apply a component to a ViewModel column

  1. Create the component folder and the required .cshtml file under EXT_Components.
  2. In MDriven Designer, select the ViewModel column that the component will render.
  3. Set the column's UIOverride to use a component.
  4. Add the tagged value Angular_Ext_Component and set its value to the component folder name.
  5. Start the Turnkey application and navigate to the ViewModel page that contains the column.

For example, if the folder is EXT_Components/plaintable, set Angular_Ext_Component=plaintable on the collection column. Turnkey then uses plaintable.cshtml instead of its normal collection rendering.

The component name must match the folder name. A mismatch between the tagged-value value, folder name, and required .cshtml filename prevents the intended component from being found.

Start with a simple column override

If you only need to replace the HTML generated for one column, create the .cshtml file and bind directly to the column data. AngularJS exposes the ViewModel data through data.

For a column named Title, this override binds an input to the column:

<input ng-model='data.Title'/>

That markup is specific to Title. To make one component reusable for different columns, use the placeholders that Turnkey replaces while compiling the override HTML:

Placeholder Replaced with Example use
[ViewModelColumnName] The runtime name of the ViewModel column data.[ViewModelColumnName]
[ViewModelClassName] The ViewModel class name vCurrent_[ViewModelClassName]
[ViewModelColumnLabel] The column's designed presentation string A displayed label or table heading

For example, save the following as the component's .cshtml file:

<label>[ViewModelColumnLabel]</label>
<input ng-model='data.[ViewModelColumnName]'/>

When assigned to a column whose runtime name is Title, the resulting binding is data.Title. You can assign the same component to another compatible column without creating a separate component folder.

Use [ViewModelClassName] when the component needs the ViewModel-class-specific current variable. For example:

<div ng-if='vCurrent_[ViewModelClassName]'>
  [ViewModelColumnLabel]
</div>

Add CSS

Add CSS when the component needs its own presentation. For a component named test1, save this as EXT_Components/test1/test1.css:

.test1background {
    background: pink;
}

Reference the class from the component HTML:

<div class="test1background"></div>

If you provide a minified stylesheet named test1.min.css, Turnkey skips test1.css. Do not expect both files to load.

Add an AngularJS directive

Add JavaScript when markup and binding are not enough. A common pattern is to define an AngularJS directive, then apply that directive as an attribute in the component's .cshtml file.

The following example adds a canvas element to the component. Save the HTML as EXT_Components/test1/test1.cshtml:

<div test1 class="test1background"></div>

Save the script with a filename containing _module, for example EXT_Components/test1/test1_module.js:

function InstallTheDirectiveFor_test1(streamingAppController) {
    streamingAppController.directive('test1', ['$document', function ($document) {
        return {
            link: function (scope, element, attr) {
                // element is this directive's HTML element.
                // scope gives the directive access to the AngularJS data context.
                var c = document.createElement('canvas');
                element[0].appendChild(c);
            }
        };
    }]);
    console.trace("test1 component Loaded");
}

InstallTheDirectiveFor_test1(angular.module(MDrivenAngularAppModule));

The directive name in streamingAppController.directive('test1', ...) must match the attribute used in the HTML: <div test1>. The link function runs for each rendered instance of that directive. Use element to work with the rendered HTML and scope to work with its AngularJS data context.

TypeScript option

You can write the directive in TypeScript and use the generated JavaScript in the component folder. The following is the TypeScript form of the preceding example:

/// <reference path="../../Scripts/typings/jquery/jquery.d.ts" />
/// <reference path="../../Scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../js/MDrivenAngularApp.ts" />

namespace test1Namespace {
    function InstallTheDirectiveFor_test1(streamingAppController) {
        streamingAppController.directive('test1', ['$document', function ($document) {
            return {
                link: function (scope, element: HTMLDivElement[], attr) {
                    let c: HTMLCanvasElement = document.createElement('canvas');
                    element[0].appendChild(c);
                }
            };
        }]);

        console.trace("test1 component Loaded");
    }

    InstallTheDirectiveFor_test1(angular.module(MDrivenAngularAppModule));
}

When compiling TypeScript, you can add a tsconfig.json file to the component folder to request JavaScript output on save:

{
  "compileOnSave": true,
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "es5"
  },
  "exclude": [
    "node_modules"
  ]
}

Ensure that the generated JavaScript file follows the _module filename rule if it must be loaded when the Angular application starts.

Make data available to the component

A component can only use data that is available in its ViewModel. This matters for charts and other displays that need supporting values in addition to the column being replaced.

For example, a chart component can receive chart values from ViewModel columns that are not displayed on the page. Set those supporting columns' Visible expressions to false, while keeping them in the ViewModel so the component can bind to them. For the complete chart setup, see Documentation:Using Google Charts.

Load scripts before components when required

Use AppWideAngularScriptIncludes for scripts that must run globally before components load. This is relevant when a component depends on an AngularJS module supplied by an external library.

For example, the TinyMCE integration adds its AngularJS module as a dependency of MDrivenAngularAppModule and loads TinyMCE through an app-wide script include. Follow Documentation:TinyMCE editor rather than copying that integration into an unrelated component.

Reusable examples and related integrations

Troubleshooting

Symptom Check
The standard control still appears Verify that the ViewModel column has UIOverride set for a component and that Angular_Ext_Component exactly matches the component folder name.
The component markup does not load Verify that <component name>.cshtml exists directly in the matching component folder.
Styles do not appear as expected Check for a matching .min.css file. If it exists, Turnkey skips the non-minified .css file.
Directive code does not run Ensure the JavaScript filename contains _module, and verify that the directive name matches the HTML attribute.
A binding is empty or does not update Confirm that the component binds to a ViewModel value that is available in the current ViewModel. Use data.[ViewModelColumnName] for a reusable column binding.

See also