TypeOrm
  • Getting Started
  • About
  • Connection
    • Working with Connection
    • Using ormconfig.json
    • Connection Options
    • Multiple connections
    • Connection APIs
  • Entity
    • What is Entity?
    • Embedded Entities
    • Entity Inheritance
    • Tree Entities
    • View Entities
    • Separating Entity Definition
  • Relations
    • What are Relations?
    • One-to-One
    • Many-to-one and One-to-Many
    • Many-to-Many
    • Eager and Lazy Relations
    • Relations FAQ
  • Entity Manager and Repository
    • What is EntityManager
    • Working with Repository
    • Find Options
    • Custom Repositories
    • Entity Manager API
    • Repository API
  • Query Builder
    • Select using Query Builder
    • Insert using Query Builder
    • Update using Query Builder
    • Delete using Query Builder
    • Working with Relations
    • Caching Results
  • Advanced Topics
    • Using CLI
    • Logging
    • Listeners and Subscribers
    • Indices
    • Transactions
    • Migrations
  • Guides
    • Active Record vs Data Mapper
    • Working with MongoDB
    • Using Validation
    • Example with Express
    • Usage with JavaScript
    • Migration from Sequelize
  • Help
    • FAQ
    • Supported Platforms
    • Decorators reference
    • Roadmap
    • Changelog
Powered by GitBook
On this page
  • Adjacency list
  • Nested set
  • Materialized Path (aka Path Enumeration)
  • Closure table
  • Note:
  • Working with tree entities

Was this helpful?

  1. Entity

Tree Entities

PreviousEntity InheritanceNextView Entities

Last updated 4 years ago

Was this helpful?

TypeORM supports the Adjacency list and Closure table patterns for storing tree structures. To learn more about hierarchy table take a look at .

Adjacency list

Adjacency list is a simple model with self-referencing. The benefit of this approach is simplicity, drawback is that you can't load big trees in all at once because of join limitations. To learn more about the benefits and use of Adjacency Lists look at . Example:

import {Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany} from "typeorm";

@Entity()
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    description: string;

    @ManyToOne(type => Category, category => category.children)
    parent: Category;

    @OneToMany(type => Category, category => category.parent)
    children: Category[];
}

Nested set

Nested set is another pattern of storing tree structures in the database. Its very efficient for reads, but bad for writes. You cannot have multiple roots in nested set. Example:

import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";

@Entity()
@Tree("nested-set")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

Materialized Path (aka Path Enumeration)

Materialized Path (also called Path Enumeration) is another pattern of storing tree structures in the database. Its simple and effective. Example:

import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";

@Entity()
@Tree("materialized-path")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

Closure table

Closure table stores relations between parent and child in a separate table in a special way. It's efficient in both reads and writes. Example:

import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";

@Entity()
@Tree("closure-table")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

You can specify closure table name and / or closure table columns names by setting optional parameter options into @Tree("closure-table", options). ancestorColumnName and descandantColumnName are callback functions, which receive primary column's metadata and return column's name.

@Tree("closure-table", {
    closureTableName: "category_closure",
    ancestorColumnName: (column) => "ancestor_" + column.propertyName,
    descendantColumnName: (column) => "descendant_" + column.propertyName,
})

Note:

Working with tree entities

To make bind tree entities to each other its important to set to children entities their parent and save them, for example:

const manager = getManager();

const a1 = new Category("a1");
a1.name = "a1";
await manager.save(a1);

const a11 = new Category();
a11.name = "a11";
a11.parent = a1;
await manager.save(a11);

const a12 = new Category();
a12.name = "a12";
a12.parent = a1;
await manager.save(a12);

const a111 = new Category();
a111.name = "a111";
a111.parent = a11;
await manager.save(a111);

const a112 = new Category();
a112.name = "a112";
a112.parent = a11;
await manager.save(a112);

To load such a tree use TreeRepository:

const manager = getManager();
const trees = await manager.getTreeRepository(Category).findTrees();

trees will be following:

[{
    "id": 1,
    "name": "a1",
    "children": [{
        "id": 2,
        "name": "a11",
        "children": [{
            "id": 4,
            "name": "a111"
        }, {
            "id": 5,
            "name": "a112"
        }]
    }, {
        "id": 3,
        "name": "a12"
    }]
}]

There are other special methods to work with tree entities through TreeRepository:

  • findTrees - Returns all trees in the database with all their children, children of children, etc.

const treeCategories = await repository.findTrees();
// returns root categories with sub categories inside
  • findRoots - Roots are entities that have no ancestors. Finds them all.

    Does not load children leafs.

const rootCategories = await repository.findRoots();
// returns root categories without sub categories inside
  • findDescendants - Gets all children (descendants) of the given entity. Returns them all in a flat array.

const children = await repository.findDescendants(parentCategory);
// returns all direct subcategories (without its nested categories) of a parentCategory
  • findDescendantsTree - Gets all children (descendants) of the given entity. Returns them in a tree - nested into each other.

const childrenTree = await repository.findDescendantsTree(parentCategory);
// returns all direct subcategories (with its nested categories) of a parentCategory
  • createDescendantsQueryBuilder - Creates a query builder used to get descendants of the entities in a tree.

const children = await repository
    .createDescendantsQueryBuilder("category", "categoryClosure", parentCategory)
    .andWhere("category.type = 'secondary'")
    .getMany();
  • countDescendants - Gets number of descendants of the entity.

const childrenCount = await repository.countDescendants(parentCategory);
  • findAncestors - Gets all parent (ancestors) of the given entity. Returns them all in a flat array.

const parents = await repository.findAncestors(childCategory);
// returns all direct childCategory's parent categories (without "parent of parents")
  • findAncestorsTree - Gets all parent (ancestors) of the given entity. Returns them in a tree - nested into each other.

const parentsTree = await repository.findAncestorsTree(childCategory);
// returns all direct childCategory's parent categories (with "parent of parents")
  • createAncestorsQueryBuilder - Creates a query builder used to get ancestors of the entities in a tree.

const parents = await repository
    .createAncestorsQueryBuilder("category", "categoryClosure", childCategory)
    .andWhere("category.type = 'secondary'")
    .getMany();
  • countAncestors - Gets the number of ancestors of the entity.

const parentsCount = await repository.countAncestors(childCategory);

Updating or removing a component's parent has not been implemented yet (). The closure table will need to be explicitly updated to do either of these operations.

this awesome presentation by Bill Karwin
this article by Matthew Schinckel
see this issue