
When creating a plugin, we canextend the Admin UI in order to expose a graphical interface to the plugin's functionality.
This is possible by definingAdminUiExtensions. UI extension is anAngular module that gets compiled into the Admin UI application bundle by thecompileUiExtensions function exported by the@vendure/ui-devkit
package. Internally, the ui-devkit package makes use of the Angular CLI to compile an optimized set of JavaScript bundles containing your extensions. The Vendure Admin UI is built with Angular, andwriting UI extensions in Angular is seamless and powerful. But we can write UI extensions usingReact, Vue, or other frameworks if not familiar with Angular.
Previously, weextended Vendure with a custom Back-In-Stock notification plugin. Now we add a list component to display the subscriptions in the Vendure Admin UI. TypeScript source files of your UI extensions must not be compiled by your regular TypeScript build task. They will instead be compiled by the Angular compiler withcompileUiExtensions()
.
Exclude them intsconfig.json
by adding a line to the "exclude" array
{"exclude":["src/plugins/**/ui/*"]}
Install the ui-devkit package withyarn add @vendure/ui-devkit -D
Using GraphQL schema first approach we write our query so that we can usecodegen
to generate the types for the extension
// src/plugins/vendure-plugin-back-in-stock/ui/components/back-in-stock-list.graphql.tsimportgqlfrom'graphql-tag';exportconstBACKINSTOCK_FRAGMENT=gql` fragment BackInStock on BackInStock { id createdAt updatedAt status email productVariant { id name stockOnHand } customer { id } }`;exportconstGET_BACKINSTOCK_SUBSCRIPTION_LIST=gql` query GetBackInStockSubscriptionList($options: BackInStockListOptions) { backInStockSubscriptions(options: $options) { items { ...BackInStock } totalItems } }${BACKINSTOCK_FRAGMENT}`;
Modifycodegen.json
to addgenerated-types
to the generates object and run it withyarn codegen
from the root of the project
//codegen.json"generated/generated-types.ts":{"schema":"http://localhost:3000/admin-api","documents":"src/plugins/**/ui/**/*.graphql.ts","plugins":[{"add":{"content":"/* eslint-disable */"}},"typescript","typescript-compatibility","typescript-operations"],"config":{"scalars":{"ID":"string"}}}
Now we can start writing the list component
// src/plugins/vendure-plugin-back-in-stock/ui/components/back-in-stock-list.component.tsimport{ChangeDetectionStrategy,Component}from'@angular/core';import{ActivatedRoute,Router}from'@angular/router';import{BaseListComponent,DataService}from'@vendure/admin-ui/core';import{BackInStockSubscriptionStatus,GetBackInStockSubscriptionList,SortOrder,}from'../../../../../generated/generated-types';import{GET_BACKINSTOCK_SUBSCRIPTION_LIST}from'./back-in-stock-list.graphql';// @ts-ignore@Component({selector:'back-in-stock-list',templateUrl:'./back-in-stock-list.component.html',styleUrls:['./back-in-stock-list.component.scss'],changeDetection:ChangeDetectionStrategy.OnPush,})exportclassBackInStockListComponentextendsBaseListComponent<GetBackInStockSubscriptionList.Query,GetBackInStockSubscriptionList.Items,GetBackInStockSubscriptionList.Variables>{filteredStatus:BackInStockSubscriptionStatus|null=BackInStockSubscriptionStatus.Created;constructor(privatedataService:DataService,router:Router,route:ActivatedRoute){super(router,route);super.setQueryFn((...args:any[])=>{returnthis.dataService.query<GetBackInStockSubscriptionList.Query>(GET_BACKINSTOCK_SUBSCRIPTION_LIST,args,);},data=>data.backInStockSubscriptions,(skip,take)=>{return{options:{skip,take,sort:{createdAt:SortOrder.ASC,},...(this.filteredStatus!=null?{filter:{status:{eq:this.filteredStatus,},},}:{}),},};},);}}
And add the template for the list component, for adding custom styles useback-in-stock-list.component.scss
in the same folder as the html template
// src/plugins/vendure-plugin-back-in-stock/ui/components/back-in-stock-list.component.html<vdr-action-bar><vdr-ab-left><divclass="filter-controls"><selectclrSelectname="status"[(ngModel)]="filteredStatus"(change)="refresh()"><option[ngValue]="null">All Subscriptions</option><optionvalue="Created">Active</option><optionvalue="Notified">Notified</option></select></div></vdr-ab-left><vdr-ab-right></vdr-ab-right></vdr-action-bar><vdr-data-table[items]="items$ | async"[itemsPerPage]="itemsPerPage$ | async"[totalItems]="totalItems$ | async"[currentPage]="currentPage$ | async"(pageChange)="setPageNumber($event)"(itemsPerPageChange)="setItemsPerPage($event)"><vdr-dt-column>ID</vdr-dt-column><vdr-dt-column>Status</vdr-dt-column><vdr-dt-column>Email</vdr-dt-column><vdr-dt-column>Product</vdr-dt-column><vdr-dt-column>Created At</vdr-dt-column><vdr-dt-column>Updated At</vdr-dt-column><ng-templatelet-subscription="item"><tdclass="left align-middle"> {{ subscription.id }}</td><tdclass="left align-middle"> {{ subscription.status }}</td><tdclass="left align-middle"><a*ngIf="subscription.customer !== null; else guestUser"[routerLink]="['/customer', 'customers', subscription.customer.id]"> {{ subscription.email }}</a><ng-template#guestUser> {{ subscription.email }}</ng-template></td><tdclass="left align-middle"><atitle="{{ 'Stock on hand - ' + subscription.productVariant.stockOnHand }}"[routerLink]="[ '/catalog', 'products', subscription.productVariant.id, { id: subscription.productVariant.id, tab: 'variants' } ]"><clr-iconshape="link"></clr-icon> {{ subscription.productVariant.name }}</a></td><tdclass="left align-middle"> {{ subscription.createdAt | date : 'mediumDate' }}</td><tdclass="left align-middle"> {{ subscription.updatedAt | date : 'mediumDate' }}</td></ng-template></vdr-data-table>
Let's add the component to it's module
// src/plugins/vendure-plugin-back-in-stock/ui/back-in-stock.module.tsimport{NgModule}from'@angular/core';import{RouterModule}from'@angular/router';import{SharedModule}from'@vendure/admin-ui/core';import{BackInStockListComponent}from'./components/back-in-stock-list.component';// @ts-ignore@NgModule({imports:[SharedModule,RouterModule.forChild([{path:'',pathMatch:'full',component:BackInStockListComponent,data:{breadcrumb:'Back-In-Stock Subscriptions'},},]),],declarations:[BackInStockListComponent],})exportclassBackInStockModule{}
And create a shared module for adding a new section to the Admin UI main nav bar containing a link to the extension
// src/plugins/vendure-plugin-back-in-stock/ui/back-in-stock-shared.module.tsimport{NgModule}from'@angular/core';import{SharedModule,addNavMenuSection}from'@vendure/admin-ui/core';// @ts-ignore@NgModule({imports:[SharedModule],providers:[addNavMenuSection({id:'back-in-stock',label:'Custom Plugins',items:[{id:'back-in-stock',label:'Back-In-Stock',routerLink:['/extensions/back-in-stock'],// Icon can be any of https://core.clarity.design/foundation/icons/shapes/icon:'assign-user',},],},// Add this section before the "settings" section'settings',),],})exportclassBackInStockSharedModule{}
Add the modules to the plugins array invendure-config.ts
// vendure-config.tsAdminUiPlugin.init({route:'admin',port:3002,adminUiConfig:{apiHost:'http://localhost',apiPort:3000,},app:compileUiExtensions({outputPath:path.join(__dirname,'../admin-ui'),extensions:[{extensionPath:path.join(__dirname,'../src/plugins/back-in-stock-plugin/ui'),ngModules:[{type:'lazy'asconst,route:'back-in-stock',ngModuleFileName:'back-in-stock.module.ts',ngModuleName:'BackInStockModule',},{type:'shared'asconst,ngModuleFileName:'back-in-stock-shared.module.ts',ngModuleName:'BackInStockSharedModule',},],},],devMode:IS_DEV?true:false,}),}),
NoticedevMode
option set to true which will compile the Admin UI app in development mode, and recompile and auto-refresh the browser on any changes to the extension source files.
Angular uses the concept of modules (NgModules) for organizing related code. These modules can be lazily loaded, which means that the code is not loaded when the app starts, but only when that code is required, keeping the main bundle small to improve performance. Shared modules are loadedeagerly, i.e. code is loaded as soon as the app loads. Modules defining new routes must be set tolazy
. Modules defining new navigations items must be set toshared
.
Finally, run withyarn dev
and see the admin UI extension in action athttp://localhost:4200/admin/
Read myprevious post to learn how the Back-In-Stock plugin was created and join this awesome community of open-source developers onslack to start your own Vendure adventure today!
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse