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.
Install the LTS version of Node.js:
If you receive a version number as output, node.js has been installed. It should look something like this:
v8.9.4
We recommend using Atom or Visual Code Studio as IDE.
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:
First, we will set up a local test environment. This will allow us to check our UI in the browser.
Use the command line to navigate to the 'Plugin-Terra-Basic' directory.
cd /your-dir/plugin-terra-basic
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 .
If you're new to Angular, we recommend checking out the angular.io tutorial: Tour of Heroes.
example-view.component.html
example-view.component.scss
example-view.component.ts
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
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 { }
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.
In this step, we will link the example-view
component to NgModule
. NgModule
defines a module that contains components, directives, pipes, and providers.
plugin-terra-basic.module.ts
.
ExampleViewComponent
.
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(); }
Next, we will implement some logic to navigate to a different view. We will create a new route for this.
src/app/plugin-terra-basic.routing.ts
.
example
.
ExampleViewComponent
.
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});
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.
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'); } }); } }
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.
<terra-2-col mobileRouting> <ptb-main-menu left></ptb-main-menu> <ptb-overview-view right></ptb-overview-view> </terra-2-col>
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.
Inspect
.
Application
.
Local Storage
.
accessToken
.
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.
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(); }
Now we can use our service inside the table service. Proceed as follows:
src/app/services/basic-table.service.ts
.
PlaceholderService
in the constructor parameters with ContactService
.
getContacts
method inside the requestTableData
method and return the response.
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`) }; } }
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 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:
ui.json
in the main Etsy plugin.