How to Build your First Angular UI Plugin

In this tutorial, we will use Angular to build a plugin UI for the plentymarkets 7 back end. As basis, we will use the Terra-Basic plugin.

Tutorial Content

  • How to set up a basic Angular UI.
  • How to use REST calls addressing our plentymarkets database.
  • How to use Terra-Components.

1. Installing Node.js

Install the LTS version of Node.js:

  1. Download Node.js
  2. Open your command line. This is the terminal on macOS and Linux, or the Command Prompt on Windows.
  3. Run node -v.

If you receive a version number as output, node.js has been installed. It should look something like this:

v8.9.4

2. Setting up an IDE

We recommend using Atom or Visual Code Studio as IDE.

3. Downloading the template

We've created the Terra-Basic plugin and will use it as a template throughout this tutorial. It includes a basic Angular application and all packages we're going to use. The Terra-Components are already installed as well.

Download or clone the plugin:

4. Setting up a local test environment

First, we will set up a local test environment. This will allow us to check our UI in the browser.

  1. Open your IDE.
  2. Go to File » Open.
  3. Open the Plugin-Terra-Basic folder.
  4. Use the command line to navigate to the 'Plugin-Terra-Basic' directory.

    cd /your-dir/plugin-terra-basic

  5. Run npm install> to install all required packages. This may take some time.
  6. Run ng serve to start your local test server.

With this, setup is complete. You can now view your project in your browser on localhost:3002. You can change the port in the file /plugin-terra-basic/config/webpack.dev.js .

5. Creating a plugin view

If you're new to Angular, we recommend checking out the angular.io tutorial: Tour of Heroes.

  1. Open your IDE's file browser and navigate to the views/example folder.
  2. You should see an overview folder and the following files:
    • example-view.component.html
      → This file is used to display your UI.
    • example-view.component.scss
      → This file is used to style your UI.
  3. Inside the directory, create a new file and call it example-view.component.ts
    → This file is used to handle all your component logic.

When you're done, the folder structure should look like this:

                plugin-terra-basic/
                    └──src/
                        └── app/
                            └── views/
                                └── example/
                                    ├── example-view.component.html
                                    ├── example-view.component.scss
                                    └── example-view.component.ts
            

Creating the component

To create the example-view component, start with the typescript file.

src/app/views/example/example-view.component.ts
            import { Component } from '@angular/core';

            @Component({
            selector:    'ptb-example-view',
            templateUrl: './example-view.component.html'
            })
            export class ExampleViewComponent
            {
            }
        

Terra Components

We developed the Terra-Components for the Terra back end of plentymarkets 7. The Terra-Components are in constant development.

Terra-Components documentation:

If you have any issues with our Terra-Components, the fastest way to get a response from our Terra dev team is to create an issue in our GitHub repository.

NgModule

In this step, we will link the example-view component to NgModule. NgModule defines a module that contains components, directives, pipes, and providers.

  1. Open plugin-terra-basic.module.ts.
  2. Line 36>: import the ExampleViewComponent.
  3. Line 59>: add the ExampleViewComponent to the NgModule declarations.
src/app/plugin-terra-basic.module.ts
                import {
                    APP_INITIALIZER,
                    NgModule
                } from '@angular/core';
                import { BrowserModule } from '@angular/platform-browser';
                import { PluginTerraBasicComponent } from './plugin-terra-basic.component';
                import { StartComponent } from './views/start/start.component';
                import { HttpModule } from '@angular/http';
                import {
                    L10nLoader,
                    TranslationModule
                } from 'angular-l10n';
                import { FormsModule } from '@angular/forms';
                import { l10nConfig } from './core/localization/l10n.config';
                import { HttpClientModule } from '@angular/common/http';
                import { RouterModule } from '@angular/router';
                import {
                    appRoutingProviders,
                    routing
                } from './plugin-terra-basic.routing';
                import { StartViewComponent } from './views/start-view.component';
                import { RouterViewComponent } from './views/router/router-view.component';
                import { MainMenuComponent } from './views/menu/main-menu.component';
                import {
                    httpInterceptorProviders,
                    TerraComponentsModule,
                    TerraNodeTreeConfig
                } from '@plentymarkets/terra-components';
                import { TableComponent } from './views/example/overview/table/table.component';
                import { FilterComponent } from './views/example/overview/filter/filter.component';
                import { OverviewViewComponent } from './views/example/overview/overview-view.component';
                import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
                import { TranslationProvider } from './core/localization/translation-provider';
                import { ContactService } from './services/contact.service';
                import { BasicTableService } from './services/basic-table.service';
                import { ExampleViewComponent } from './views/example/example-view.component'; // <--- Import the component

                @NgModule({
                    imports:      [
                        BrowserModule,
                        BrowserAnimationsModule,
                        HttpModule,
                        FormsModule,
                        HttpClientModule,
                        TranslationModule.forRoot(l10nConfig, { translationProvider: TranslationProvider }),
                        RouterModule.forRoot([]),
                        TerraComponentsModule,
                        routing
                    ],
                    declarations: [
                        PluginTerraBasicComponent,
                        RouterViewComponent,
                        MainMenuComponent,
                        StartViewComponent,
                        StartComponent,
                        TableComponent,
                        FilterComponent,
                        OverviewViewComponent,
                        ExampleViewComponent // <--- declare the component, so that you can use it in your project.
                    ],
                    providers:    [
                        {
                            provide:    APP_INITIALIZER,
                            useFactory: initL10n,
                            deps:       [L10nLoader],
                            multi:      true
                        },
                        httpInterceptorProviders,
                        appRoutingProviders,
                        TerraNodeTreeConfig,
                        ContactService,
                        BasicTableService
                    ],
                    bootstrap:    [
                        PluginTerraBasicComponent
                    ]
                })
                export class PluginTerraBasicModule
                {
                    constructor(public l10nLoader:L10nLoader)
                    {
                        this.l10nLoader.load();
                    }
                }

                function initL10n(l10nLoader:L10nLoader):Function
                {
                    return ():Promise => l10nLoader.load();
                }
            

6. Navigation

Next, we will implement some logic to navigate to a different view. We will create a new route for this.

  1. Open src/app/plugin-terra-basic.routing.ts.
  2. Add a new object to the array of children that belongs to the plugin route object.
  3. Add the path property and set its value to the string example.
  4. Add the component property and set its value to the type ExampleViewComponent.
  5. Import the Example View Component.
  6. Add the data property. Set its value to Object and set the label property of that object to the string example.
            import { ModuleWithProviders } from '@angular/core';
            import {
                RouterModule,
                Routes
            } from '@angular/router';
            import { StartViewComponent } from './views/start-view.component';
            import { RouterViewComponent } from './views/router/router-view.component';
            import { ExampleViewComponent } from './views/example/example-view.component';

            const appRoutes:Routes = [
                {
                    path: '',
                    redirectTo: 'plugin',
                    pathMatch: 'full',
                },
                {
                    path:      'plugin',
                    component: RouterViewComponent,
                    children:  [
                        {
                            path: '',
                            data:        {
                                label:       'menu'
                            },
                            redirectTo: 'start',
                            pathMatch: 'full'
                        },
                        {
                            path:      'start',
                            component: StartViewComponent,
                            data:      {
                                label: 'start'
                            }
                        },
                        {
                            path:      'example',
                            component: ExampleViewComponent,
                            data:      {
                                label: 'example'
                            }
                        }
                    ]
                },

            ];

            export const appRoutingProviders:Array = [];

            export const routing:ModuleWithProviders =
                RouterModule.forRoot(appRoutes, {useHash:true});
        

Adding a menu entry

In this step, we will create a new menu entry for our route. For this, we will use TerraNodeTree. This component defines a tree structure made up of nodes that can be used to render a multi-level menu tree.

  1. Open main-menu.component.ts
  2. Uncomment lines 40 to 49.
src/app/views/example/menu/main-menu.component.tss
                import {
                    Component,
                    OnInit
                } from '@angular/core';
                import { Language } from 'angular-l10n';
                import {
                    TerraNodeTreeConfig
                } from '@plentymarkets/terra-components';
                import { Router } from '@angular/router';
                import { TranslationService } from 'angular-l10n';

                @Component({
                    selector:    'ptb-main-menu',
                    templateUrl: './main-menu.component.html'
                })
                export class MainMenuComponent implements OnInit
                {
                    @Language()
                    public lang:string;

                    constructor(protected treeConfig:TerraNodeTreeConfig<{}>,
                                private router:Router,
                                private translation:TranslationService)
                    {
                    }

                    public ngOnInit():void
                    {
                        this.treeConfig.addNode({
                            id:        'start',
                            name:      this.translation.translate('start'),
                            isVisible: true,
                            isActive:  this.router.isActive('plugin/start', true),
                            onClick:   ():void =>
                                    {
                                        this.router.navigateByUrl('plugin/start');
                                    }
                        });

                        this.treeConfig.addNode({
                            id:        'example',
                            name:      this.translation.translate('example'),
                            isVisible: true,
                            isActive:  this.router.isActive('plugin/example', true),
                            onClick:   ():void =>
                                    {
                                        this.router.navigateByUrl('plugin/example');
                                    }
                        });
                    }
                }
            

Adding table and filter

Next, we will add a table and a filter that will appear when clicking on our new menu entry.

Open src/app/views/example/example-view.component.html. Inside the terra-2-col selector, under ptb-main-menu selector, add the ptb-overview-view element and assign to it the right attribute.

                
                    
                    
                
            

Adding a service

In this step we will create a service to provide our table with data from our database.

Open the src/app/services folder. It already contains the basic-table service. This service handles data table logic like mapping the raw data to the table rows. Now we need to create another service that will provide the raw data.

Create a contact service. You can do this by using ng generate service and then entering services/contact when prompted for a name, or by creating it manually. When using the generate option, you will also be provided with a spec file which you can use to create tests.

src/app/views/example/menu/main-menu.component.tss
                import { Injectable } from '@angular/core';
                import { HttpClient } from '@angular/common/http';
                import { ContactInterface } from '../interfaces/contact.interface';
                import { Observable } from 'rxjs/internal/Observable';
                import { TerraPagerInterface } from '@plentymarkets/terra-components';

                @Injectable()
                export class ContactService
                {
                    constructor(private http:HttpClient)
                    {
                    }

                    public getContacts():Observable>
                    {
                        const url:string = 'http://master.login.plentymarkets.com/rest/accounts/contacts';  // <--- replace domain with your plentymarkets test system domain

                        return this.http.get>(url);
                    }
                }
            

To use the service and be able to send REST calls, you need to get an authentication token.

  1. Log in to your plentymarkets test system.
  2. Open your browser dev tools.
  3. Right click and click on Inspect.
  4. Go to Application.
  5. Open Local Storage.
  6. Click on your connection example: http://master.login.plentymarkets.com.
  7. This will open a list where you can copy the accessToken.
  8. Open plugin-terra-basic.component.ts and go to line 17. Paste the copied token into the accessToken variable.

You are ready to use the service. However, note that the token will change every time your login session expires. This means you may have to repeat this step several times during the course of development.

Linking your service to the module

If you used the generate option, the service should be added to the module automatically.

If not, you may need to add it to the NgModule manually. Import the service as shown in line 36. Add the service to providers as shown in line 72.

src/app/plugin-terra-basic.module.ts
                import {
                    APP_INITIALIZER,
                    NgModule
                } from '@angular/core';
                import { BrowserModule } from '@angular/platform-browser';
                import { PluginTerraBasicComponent } from './plugin-terra-basic.component';
                import { StartComponent } from './views/start/start.component';
                import { HttpModule } from '@angular/http';
                import {
                    L10nLoader,
                    TranslationModule
                } from 'angular-l10n';
                import { FormsModule } from '@angular/forms';
                import { l10nConfig } from './core/localization/l10n.config';
                import { HttpClientModule } from '@angular/common/http';
                import { RouterModule } from '@angular/router';
                import {
                    appRoutingProviders,
                    routing
                } from './plugin-terra-basic.routing';
                import { StartViewComponent } from './views/start-view.component';
                import { RouterViewComponent } from './views/router/router-view.component';
                import { MainMenuComponent } from './views/menu/main-menu.component';
                import {
                    httpInterceptorProviders,
                    TerraComponentsModule,
                    TerraNodeTreeConfig
                } from '@plentymarkets/terra-components';
                import { TableComponent } from './views/example/overview/table/table.component';
                import { FilterComponent } from './views/example/overview/filter/filter.component';
                import { OverviewViewComponent } from './views/example/overview/overview-view.component';
                import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
                import { TranslationProvider } from './core/localization/translation-provider';
                import { BasicTableService } from './services/basic-table.service';
                import { ExampleViewComponent } from './views/example/example-view.component';
                import { ContactService } from './services/contact.service'; // <--- Import the service

                @NgModule({
                    imports:      [
                        BrowserModule,
                        BrowserAnimationsModule,
                        HttpModule,
                        FormsModule,
                        HttpClientModule,
                        TranslationModule.forRoot(l10nConfig, { translationProvider: TranslationProvider }),
                        RouterModule.forRoot([]),
                        TerraComponentsModule,
                        routing
                    ],
                    declarations: [
                        PluginTerraBasicComponent,
                        RouterViewComponent,
                        MainMenuComponent,
                        StartViewComponent,
                        StartComponent,
                        TableComponent,
                        FilterComponent,
                        OverviewViewComponent,
                        ExampleViewComponent
                    ],
                    providers:    [
                        {
                            provide:    APP_INITIALIZER,
                            useFactory: initL10n,
                            deps:       [L10nLoader],
                            multi:      true
                        },
                        httpInterceptorProviders,
                        appRoutingProviders,
                        TerraNodeTreeConfig,
                        BasicTableService,
                        ContactService,
                    ],
                    bootstrap:    [
                        PluginTerraBasicComponent
                    ]
                })
                export class PluginTerraBasicModule
                {
                    constructor(public l10nLoader:L10nLoader)
                    {
                        this.l10nLoader.load();
                    }
                }

                function initL10n(l10nLoader:L10nLoader):Function
                {
                    return ():Promise => l10nLoader.load();
                }
            

Using the service inside the table service

Now we can use our service inside the table service. Proceed as follows:

  1. Open src/app/services/basic-table.service.ts.
  2. Exchange PlaceholderService in the constructor parameters with ContactService.
  3. Call the getContacts method inside the requestTableData method and return the response.
  4. Define the return type of the requestTableData method.
                import {
                    EventEmitter,
                    Injectable
                } from '@angular/core';
                import {
                    TerraDataTableBaseService,
                    TerraDataTableCellInterface,
                    TerraDataTableRowInterface,
                    TerraPagerInterface,
                    TerraPagerParameterInterface
                } from '@plentymarkets/terra-components';
                import { ContactInterface } from '../interfaces/contact.interface';
                import { ContactService } from './contact.service';
                import { Observable } from 'rxjs/internal/Observable';
                import { tap } from 'rxjs/operators';


                @Injectable()
                export class BasicTableService
                    extends TerraDataTableBaseService
                {
                    public dataLoaded:EventEmitter> = new EventEmitter>();

                    constructor(private contactService:ContactService) // <--- Replace the PlaceholderService with the ContactService.
                    {
                        super();
                    }

                    public requestTableData():Observable> // <--- Set the correct return type.
                    {
                        return this.contactService.getContacts(); // <--- Get the contact data from the contact service.
                    }

                    public dataToRowMapping(rowData:ContactInterface):TerraDataTableRowInterface
                    {
                        const cellList:Array = [
                            {
                                data: rowData.id
                            },
                            {
                                data: rowData.firstName
                            },
                            {
                                data: rowData.lastName
                            }
                        ];

                        return {
                            cellList:      cellList,
                            data:          rowData,
                            clickFunction: ():void => alert(`Row with id ${rowData.id} clicked`)
                        };
                    }
                }
            

Enable cross origin

If you get errors due to cross origin resource sharing we need to allow it by using a plugin. For example the Allow-Control-Allow-Origin in Chrome Activate the plugin and allow all resources from your test system domain.

At this point of the tutorial, your Angular UI should look like this:

8. Adding UI to Plugin

At this point you should have a working Angular application. To combine your Angular UI with the plugin, follow the Combine Angular UI with plugin tutorial.

We will continue to improve this tutorial and add more features to the shown plugin.

Further steps:

Is this article helpful?

 

Thank you for your Feedback

you can close this field now!