Skip to content

Tree Page

The Tree Module contains a Tree Page Component that we can use to display the hierarchy of an entity.

The Tree Page uses the TreePageEffect, which has a default implementation for working with GraphQL Queries.

Adding a Tree Page for a GQL Query

The Tree Page needs a class for configuring the tree and handling it's actions. We call that a TreePageEffect. For creating Tree Page, we need to create a class implementing TreePageEffect. Also, for displaying the tree properly, make sure to implement a hierarchical query as stated here.

TypeScript
// A unique id to identify this page, whenever we wanna show a page
// using this configuration, we will reference it by this id.
export const POSITION_TREE_PAGE_ID = 'positions-list';

@Injectable()
export class PositionTreePageEffect extends TreePageEffect<any> {
    // This where we should use the previously defined ID.
    protected pageId = POSITION_TREE_PAGE_ID;

    // This is the name of the GQL query that we exposed in the back-end.
    protected queryName = 'positionChildren';

    // The id field, usually the correlationId, of this entity.
    // this is used for selection, and identifying an item of the tree
    protected idField: 'correlationId';

    // This is the title of the Tree Page. We'd recommend to localize this text.
    protected title = 'Positions';

    // All actions we declare in the tree (in the constructor) we will
    // handle here.
    public newButtonClicked$ = createEffect(
        () =>
            this.actions.pipe(
                ofType(newPosition),
                tap(() => {
                    this.router.navigate(['positions', 'new']);
                })
            ),
        { dispatch: false }
    );

    constructor(
        private actions: Actions,
        private router: Router,
        tree: TreePageService
    ) {
        super(actions, tree);

        // Customize fields of the tree.
        this.customizeTree((b) =>
            b.field('correlationId', (b) => b.hiddenByDefault())
        );

        // Add actions
        .addToolbarAction({
            label: 'New',
            action: newPosition()
        })
    }
}

Once we do this, we need to register the Effect in our Feature Module.

TypeScript
1
2
3
4
5
6
7
8
@NgModule({
    imports: [
        // [..]
        // Register the effect with NGRX
        EffectsModule.forFeature([PositionTreePageEffect])
    ]
})
export class AdminCenterPositionsModule {}

Finally, in our Routing module we can use createTreeRoute to render a Form by ID at a path:

TypeScript
1
2
3
4
5
6
7
const routes: Routes = [createTreeRoute('', POSITION_TREE_PAGE_ID)];

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule]
})
export class AdminCenterPositionsRoutingModule {}

Note

If you don't have a routing module, you'll need to create it and use it in AdminCenter (or other app)

Customizing the Tree

Once we have our tree, we might want need to perform some customizations according to our business requirements. We can call this.customizeTree in the constructor, and will receive a TreeBuilder that we'll use to customize the tree.

TypeScript
this.customizeTree((b) => /* .. */);

The most common thing to do is to customize the tree fields, that being said, before customizing anything make sure the defaults don't fit for your case, since for most cases the defaults are fine.

Customizing Actions

To define actions that can be performed for each item of the tree we can use either the addItemAction or addItemActions methods of the TreeBuilder.

TypeScript
1
2
3
4
5
6
7
8
// Your action
const removeButtonClicked = createTreeNodeAction('[Source] Remove button clicked');

// Your tree effect
this.customizeTree((b) => b.addItemAction({
    label: $localize':@@BTN_REMOVE_ENTITY:BTN_REMOVE_ENTITY',
    action: removeButtonClicked({})
}));

The user then can trigger certain action by clicking the corresponding button rendered on each node of the tree.

Toolbar actions

In a similar way we can define toolbar actions, whose buttons will appear on the toolbar of the tree, by using the addToolbarAction or addToolbarActions methods of the TreeBuilder.

TypeScript
// Your action
const newButtonClicked = CreateAction('[Source] New button clicked');

// Your tree effect
this.customizeTree((b) => b.addToolbarAction({
    label: $localize':@@BTN_NEW_ENTITY:BTN_NEW_ENTITY',
    action: newButtonClicked()
}));

## Customizing Fields

We can customize a field using the `field` method of the `TreeBuilder`, which
gives us a `TreeFieldBuilder`.

```typescript
this.customizeTree((b) => b.field('correlationId', (c) => c.hiddenByDefault()));

If a field is an object type, we can keep calling field on the TreeFieldBuilder just like we did with the TreeBuilder.

Hidden By Default

The Tree allows the user to choose which fields they want to see at any given time. However, we usually want to provide a sane set of default fields to show for our trees. We can do that by using the hiddenByDefault() method:

TypeScript
b.field('correlationId', (c) => c.hiddenByDefault());

The hiddenByDefault() method, will hide the field unless the user has chosen to show it, by using the fields chooser, or if a pre-selected "folder definition" (UX Roles, concept we don't have yet) is showing the field.

If the field is an object type, hiding the field will hide all of the children as well. This is a feature.

Child fields can override the parent by calling hiddenByDefault(true), which is what we usually do for object fields: we hide all fields, and show a field that represent the object, like it's name.

TypeScript
b.field('defaultPriority', (b) =>
    b
        .hiddenByDefault() // hide all object fields
        .field('displayName', (b) =>
            b
                .displayName('Default Priority')
                // Override the displayName so it's not hidden by default
                .hiddenByDefault(false)
        )
);

Overriding the default behavior

By default, the tree will show all fields, unless hiddenByDefault has been called on them.

If we want the opposite behavior, we can call hiddenByDefault on the TreeBuilder, which causes all fields to be hidden by default unless overridden by using hiddenByDefault(true) on the field.

TypeScript
this.customizeTree((b) => b.hiddenByDefault());

Display Name

The display name is the label that is displayed to the user to represent the field. By default, it will be calculated from the name.

The default is to convert aPascalCasedField to A Pascal Cased Field. In the case of nested objects, the parent's name is appended.

TypeScript
this.customizeTree((b) => b.displayName('The Name'));