Skip to main content
The frontend extension is an Angular library that integrates Venzia Datalinks functionality into the Alfresco Content App. Built with Angular 16, Material Design, and NgRx for state management, it provides UI components and services for managing datalinks.

Architecture Overview

The library follows Angular best practices with a modular architecture, feature store pattern using NgRx, and standalone components.

Angular Components

Reusable UI components for datalink management

NgRx Store

State management with actions, reducers, and effects

Services

API communication and business logic

ADF Integration

Deep integration with Alfresco Development Framework

Module Structure

VenziaDatalinkModule

The main Angular module that bundles all functionality:
venzia-datalink.module.ts
import { NgModule } from '@angular/core';
import { ExtensionService, provideExtensionConfig } from '@alfresco/adf-extensions';
import { TranslationService } from '@alfresco/adf-core';
import * as rules from '@venzia/venzia-datalink/rules';
import { VenziaDatalinkStoreModule } from './store';

@NgModule({
  declarations: [DatalinkDialogComponent, AddDatalinkDialogComponent],
  imports: [
    VenziaDatalinkStoreModule,
    TranslateModule,
    MatDialogModule,
    MatIconModule,
    DatalinkListComponent,
    DatalinkAddPanelComponent,
    MatButtonModule
  ],
  exports: [],
  providers: [provideExtensionConfig(['venzia-datalink.plugin.json'])]
})
export class VenziaDatalinkModule {
  constructor(translation: TranslationService, extensions: ExtensionService) {
    translation.addTranslationFolder('datalink', 'assets/datalink');

    extensions.setEvaluators({
      'datalink.canDatalink': rules.canDatalink
    });
  }
}
Key Features:
  • Registers translation resources
  • Configures extension evaluators for conditional UI display
  • Imports NgRx store module for state management
  • Provides plugin configuration

Public API

The library exports the following public API:
public-api.ts
export * from './lib/services/venzia-datalink.service';
export * from './lib/components/datalink-manager.component';
export * from './lib/venzia-datalink.module';

Core Services

VenziaDatalinkApiService

Handles communication with the backend web scripts:
venzia-datalink-api.service.ts
import { Injectable } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-content-services';
import { LogService } from '@alfresco/adf-core';
import { from, Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class VenziaDatalinkApiService {
  constructor(
    private apiService: AlfrescoApiService, 
    private logService: LogService
  ) {}

  loadDataLink(): Observable<any> {
    return from(
      this.executeWebScript(
        'GET', 
        'aqua/datalink/v1/list', 
        null, 
        null, 
        'service', 
        null
      )
    ).pipe(
      catchError((err) => this.handleError(err))
    );
  }

  private executeWebScript(
    httpMethod: string,
    scriptPath: string,
    scriptArgs?: any,
    contextRoot?: string,
    servicePath?: string,
    postBody?: any
  ): Promise<any> {
    contextRoot = contextRoot || 'alfresco';
    servicePath = servicePath || 'service';

    const allowedMethod: string[] = ['GET', 'POST', 'PUT', 'DELETE'];
    if (!httpMethod || allowedMethod.indexOf(httpMethod) === -1) {
      throw new Error('method allowed value GET, POST, PUT and DELETE');
    }

    const contentTypes = ['application/json'];
    const accepts = ['application/json', 'text/html'];
    
    return this.apiService
      .getInstance()
      .contentClient.callApi(
        scriptPath, 
        httpMethod, 
        {}, 
        scriptArgs, 
        {}, 
        {}, 
        postBody, 
        contentTypes, 
        accepts, 
        null, 
        contextRoot + '/' + servicePath
      );
  }
}
Responsibilities:
  • Calls the backend web script at /alfresco/service/aqua/datalink/v1/list
  • Handles HTTP errors and logging
  • Uses Alfresco JS API for authenticated requests

VenziaDatalinkService

Manages dialog interactions and user actions:
venzia-datalink.service.ts
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { NotificationService } from '@alfresco/adf-core';
import { NodeEntry } from '@alfresco/js-api';
import { DatalinkDialogComponent } from '../dialogs/datalink-dialog/datalink-dialog.component';

@Injectable({
  providedIn: 'root'
})
export class VenziaDatalinkService {
  constructor(
    private dialogRef: MatDialog, 
    private notificationService: NotificationService
  ) {}

  dataLinkDoc(node: NodeEntry): void {
    if (node?.entry && node?.entry.properties) {
      this.dialogRef.open(DatalinkDialogComponent, {
        data: { nodeId: node.entry.id },
        panelClass: 'aca-permissions-dialog-panel',
        width: '730px'
      });
    } else {
      this.notificationService.showError(
        'DATALINK.MESSAGES.ERRORS.NO_PROPERTIES'
      );
    }
  }
}
Features:
  • Opens datalink dialog for selected nodes
  • Validates node properties before opening dialog
  • Displays error notifications for invalid selections

State Management (NgRx)

Store Module

The library uses NgRx for predictable state management:
venzia-datalink-store.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { venziaDatalinkReducer } from './reducers/venzia-datalink.reducer';
import { VenziaDatalinkEffects } from './effects/venzia-datalink.effects';

@NgModule({
  imports: [
    StoreModule.forFeature('datalink', venziaDatalinkReducer),
    EffectsModule.forFeature([VenziaDatalinkEffects])
  ]
})
export class VenziaDatalinkStoreModule {}

Actions

Defines the action types for datalink operations:
venzia-datalink.actions.ts
import { Action } from '@ngrx/store';
import { NodeEntry } from '@alfresco/js-api';

export enum VenziaDatalinkActionTypes {
  DatalinkOpen = 'DATALINK_OPEN_ACTION'
}

export class DataLinkOpenAction implements Action {
  readonly type = VenziaDatalinkActionTypes.DatalinkOpen;
  constructor(public payload: NodeEntry) {}
}

Effects

Handles side effects like opening dialogs:
venzia-datalink.effects.ts
import { inject, Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { map, take } from 'rxjs/operators';
import { getAppSelection } from '@alfresco/aca-shared/store';

@Injectable()
export class VenziaDatalinkEffects {
  private actions$ = inject(Actions);
  private store = inject(Store);
  private venziaDatalinkService = inject(VenziaDatalinkService);
  private notificationService = inject(NotificationService);

  dataLinkDoc$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType<DataLinkOpenAction>(VenziaDatalinkActionTypes.DatalinkOpen),
        map((action) => {
          if (action.payload) {
            this.venziaDatalinkService.dataLinkDoc(action.payload);
          } else {
            this.store
              .select(getAppSelection)
              .pipe(take(1))
              .subscribe((selection) => {
                if (!selection.isEmpty) {
                  this.venziaDatalinkService.dataLinkDoc(selection.first);
                } else {
                  this.notificationService.showError(
                    'DATALINK.MESSAGES.ERRORS.EMPTY_SELECTION'
                  );
                }
              });
          }
        })
      ),
    { dispatch: false }
  );
}
Effect Logic:
  1. Listens for DATALINK_OPEN_ACTION
  2. Uses payload node if provided, otherwise gets current selection from store
  3. Opens datalink dialog or shows error notification

UI Components

DataLinkManagerComponent

The main container component for datalink management:
datalink-manager.component.ts
import { Component, Input, ViewChild, Output, EventEmitter } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppStore, SnackbarErrorAction } from '@alfresco/aca-shared/store';

@Component({
  selector: 'aqua-datalink-manager',
  templateUrl: './datalink-manager.component.html',
  standalone: true,
  imports: [CommonModule, DatalinkListComponent]
})
export class DataLinkManagerComponent implements OnInit {
  @Input() nodeId: string;
  @ViewChild('datalinkList') datalinkList: DatalinkListComponent;
  @Output() select: EventEmitter<any> = new EventEmitter();

  toggleStatus = false;

  constructor(private store: Store<AppStore>) {}

  onSelect(selectionList: any[]) {
    this.select.emit(selectionList);
  }

  onError(errorMessage: string) {
    this.store.dispatch(new SnackbarErrorAction(errorMessage));
  }

  onUpdate() {
    this.datalinkList.reload();
  }

  openAddDatalinkDialog(event: Event) {
    this.datalinkList.openAddDatalinkDialog(event);
  }

  removeSelectionRows(event: Event) {
    this.datalinkList.deleteSelectRows(event);
  }
}
Component Features:
  • Manages datalink list child component
  • Emits selection events to parent components
  • Dispatches error actions to NgRx store
  • Delegates actions to child components
DataLinkManagerComponent is a standalone component, following Angular’s modern component model.

Component Hierarchy

DataLinkManagerComponent (aqua-datalink-manager)
├── DatalinkListComponent
│   ├── Shows list of datalinks for a node
│   └── Handles selection and deletion
└── DatalinkAddPanelComponent
    ├── Search and add new datalinks
    └── Integrates with datalink search dialog

Dialogs

DatalinkDialogComponent

Main dialog for managing datalinks on a document:
@Component({
  selector: 'aqua-datalink-dialog',
  templateUrl: './datalink-dialog.component.html'
})
export class DatalinkDialogComponent {
  constructor(
    @Inject(MAT_DIALOG_DATA) public data: { nodeId: string },
    private dialogRef: MatDialogRef<DatalinkDialogComponent>
  ) {}
}

AddDatalinkDialogComponent

Dialog for adding new datalinks to a document:
@Component({
  selector: 'aqua-add-datalink-dialog',
  templateUrl: './datalink-add-dialog.component.html'
})
export class AddDatalinkDialogComponent {
  // Implementation for adding datalinks
}

Additional Services

VenziaDatalinkRestServiceHandles REST operations for datalink CRUD operations:
  • Create datalink associations
  • Update datalink metadata
  • Delete datalink relationships

Pipes

OrderByPipe

Custom pipe for sorting datalink lists:
order-by.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'orderBy',
  standalone: true
})
export class OrderByPipe implements PipeTransform {
  transform(array: any[], field: string, order: 'asc' | 'desc' = 'asc'): any[] {
    // Sorting implementation
  }
}

Extension Configuration

The library integrates with ADF Extensions framework:
venzia-datalink.plugin.json
{
  "$version": "1.0.0",
  "$id": "venzia.datalink",
  "$name": "Venzia Datalink Plugin",
  "actions": [
    {
      "id": "venzia.datalink.open",
      "type": "DATALINK_OPEN_ACTION",
      "payload": "$node"
    }
  ],
  "rules": [
    {
      "id": "datalink.canDatalink",
      "type": "core.every",
      "parameters": [
        { "type": "rule", "value": "app.selection.file" },
        { "type": "rule", "value": "app.selection.hasProperties" }
      ]
    }
  ]
}

Dependencies

The library has the following peer dependencies:
package.json
{
  "name": "@venzia/venzia-datalink",
  "version": "0.0.1",
  "peerDependencies": {
    "@angular/common": "16.2.9",
    "@angular/compiler": "16.2.9",
    "@angular/core": "16.2.9",
    "@angular/forms": "16.2.9",
    "@angular/material": "16.2.9"
  },
  "dependencies": {
    "tslib": "^2.3.0"
  }
}
The library is built for Angular 16 and requires compatible versions of Angular Material and Alfresco ADF.

Project Structure

app/projects/venzia-datalink/
├── src/
│   ├── public-api.ts                    # Public exports
│   └── lib/
│       ├── venzia-datalink.module.ts    # Main module
│       ├── components/
│       │   ├── datalink-manager.component.ts
│       │   ├── datalink-list/
│       │   ├── datalink-search/
│       │   └── datalink-add-panel/
│       ├── dialogs/
│       │   ├── datalink-dialog/
│       │   └── datalink-add-dialog/
│       ├── services/
│       │   ├── venzia-datalink.service.ts
│       │   ├── venzia-datalink-api.service.ts
│       │   ├── venzia-datalink-rest.service.ts
│       │   └── venzia-datalink-search-dialog.service.ts
│       ├── store/
│       │   ├── venzia-datalink-store.module.ts
│       │   ├── actions/
│       │   ├── effects/
│       │   ├── reducers/
│       │   ├── states/
│       │   └── initial-state.ts
│       └── pipes/
│           └── order-by.pipe.ts
├── rules/
│   └── src/
│       ├── public-api.ts
│       └── app.rules.ts
├── package.json
└── test.ts

Integration with Content App

To use the library in Alfresco Content App:
1

Install the library

Add to your Content App’s package.json:
{
  "dependencies": {
    "@venzia/venzia-datalink": "^0.0.1"
  }
}
2

Import the module

Add to your app.module.ts:
import { VenziaDatalinkModule } from '@venzia/venzia-datalink';

@NgModule({
  imports: [
    VenziaDatalinkModule,
    // ... other modules
  ]
})
3

Configure extensions

The plugin automatically registers with ADF Extensions.
4

Use the component

Use the manager component in your templates:
<aqua-datalink-manager [nodeId]="selectedNode.id"></aqua-datalink-manager>

Development

Building the Library

ng build @venzia/venzia-datalink

Running Tests

ng test @venzia/venzia-datalink

Watching for Changes

ng build @venzia/venzia-datalink --watch

Next Steps

Backend Module

Learn about the Alfresco repository services

Database Mock

Set up the test server for development

Build docs developers (and LLMs) love