[Из песочницы] Пишу TreeView на Angular 2

Вдохновившись статьей «Порог вхождения в Angular 2 — теория и практика», решил тоже написать статью про свои муки творчества.

У меня есть большой проект, написанный на ASP.NET WebForms. В нем намешано много всякого, и постепенно мне это всё перестало нравиться. Решил я попробовать переписать всё на чем-нибудь современном. Angular 2 мне приглянулся сразу, и я решил пробовать его. Задача определилась такая: написать новый frontend, прикрутив его к существующему backend, с минимальными переделками последнего. Новый frontend должен быть UI-совместимым со старым, чтобы конечный пользователь ничего не заметил.

Итого имеем такой стэк: backend — ASP.NET Web API, Entity Framework, MS SQL; frontend — Angular 2; тема Bootstrap 3.

Сразу покажу результат TreeView:

image
Процесс настройки Angular 2 в Visual Studio описывать не буду, на просторах этого полно. Единственное, что пришлось добавить, это настройку в web.config для редиректа route-запросов на index.html:

кусок web.config

          
          
                  
                          
                                  
                                  
                                          
                                          
                                  
                                  
                          
                  
          




Все успешно взлетело. Статик файлы грузятся правильно, api отрабатывают контроллеры web api, остальные маршруты всегда обрабатывает index.html.

Прежде чем начинать писать конечные точки, решил сначала написать некоторые контролы-аналоги WebForm’s. Чаще всего конечно используется ListView и FormView. Но начать я решил с простенького TreeView, он тоже нужен в нескольких формах.

Для уменьшения трафика решил сделать загрузку только необходимых узлов дерева. При инициализации запрашиваю только верхний уровень.

При раскрытии узла проверяем наличие потомков, при отсутствии генерируем событие onRequestNodes. При выделении пользователем узла — генерируем событие onSelectedChanged. Иконки fontawesome.

Компонент имеет два входящих параметра: Nodes — список узлов на данном уровне, SelectedNode — выбранный пользователем узел. Два события: onSelectedChanged — смена выбранного пользователем узла, onRequestNodes — запрос узлов, при необходимости. @Input параметры распространяются от родителя к потомкам (вглубь иерархии). @Output () события распростаняются от потомков к родителям (наружу иерархии). Компонент рекурсивный — каждый новый уровень иерархии обрабатывает свой экземпляр компонента.

treeview.component.ts
import {Component, Input, Output, EventEmitter} from 'angular2/core';

export interface ITreeNode {
        id: number;
        name: string;
        children: Array;
}

@Component({
        selector: "tree-view",
        templateUrl: "/app/components/treeview/treeview.html",
        directives: [TreeViewComponent]
})
export class TreeViewComponent {

        @Input() Nodes: Array;
        @Input() SelectedNode: ITreeNode;

        @Output() onSelectedChanged: EventEmitter = new EventEmitter();
        @Output() onRequestNodes: EventEmitter = new EventEmitter();

        constructor() { }

        onSelectNode(node: ITreeNode) {
                this.onSelectedChanged.emit(node);
        }

        onExpand(li: HTMLLIElement, node: ITreeNode) {
                if (this.isExpanden(li)) {
                        li.classList.remove('expanded');
                }
                else {
                        li.classList.add('expanded');

                        if (node.children.length == 0) {
                                this.onRequest(node);
                        }
                }
        }

        onRequest(parent: ITreeNode) {
                this.onRequestNodes.emit(parent);
        }

        isExpanden(li: HTMLLIElement) {
                return li.classList.contains('expanded');
        }
}



treeview.html
  • {{node.name}}


Стили сделал отдельным файлом.

treeview.css
tree-view .treenodes {
        list-style-type: none;
        padding-left: 0;
}

tree-view tree-view .treenodes {
        list-style-type: none;
        padding-left: 16px;
}

tree-view .nodebutton {
        cursor: pointer;
}

tree-view .nodetext {
        padding-left: 3px;
        padding-right: 3px;
        cursor: pointer;
}



Как использовать:

sandbox.component.ts
import {Component, OnInit} from 'angular2/core';
import {NgClass} from 'angular2/common';
import {TreeViewComponent, ITreeNode} from '../treeview/treeview.component';
import {TreeService} from '../../services/tree.service';

@Component({
        templateUrl: '/app/components/sandbox/sandbox.html',
        directives: [NgClass, TreeViewComponent]
})
export class SandboxComponent implements OnInit {

        Nodes: Array;
        selectedNode: ITreeNode; // нужен для отображения детальной информации по выбранному узлу.

        constructor(private treeService: TreeService) {
        }

        // начальное заполнение верхнего уровня иерархии
        ngOnInit() {
                this.treeService.GetNodes(0).subscribe(
                        res => this.Nodes = res,
                        error => console.log(error)
                );
        }
        // обработка события смены выбранного узла
        onSelectNode(node: ITreeNode) {
                this.selectedNode = node;
        }
        // обработка события вложенных узлов
        onRequest(parent: ITreeNode) {
                this.treeService.GetNodes(parent.id).subscribe(
                        res => parent.children = res,
                        error=> console.log(error));
        }
}



sandbox.html
Напоминаю, у меня bootstrap 3.


tree.service.ts
Самый примитивный сервис
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/Rx';

@Injectable()
export class TreeService {
        constructor(public http: Http) {

        }
        GetNodes(parentId: number) {
                return this.http.get("/api/tree/" + parentId.toString())
                        .map(res=> res.json());
        }
}



Получился вот такой «каркас» treeview. В дальнейшем можно сделать свойства для иконок, для выделения, чтобы отвязать treeview от bootstrap 3.

Backend описывать не буду, там ничего интересного, обычный web api контроллер и entity framework.

Следующий подопытный будет asp: ListView. В моём проекте он используется повсюду и по всякому. С встроенными Insert, Update templates и без, с множественной сортировкой, с пейджингом, с фильтрами…

Готов к конструктивной критике.

© Habrahabr.ru