Пользовательский компонент выдвижного меню: заполнение динамическими данными компонента и стилизация с помощью анимации Angular и SCSS


Цели

Часть 1 (эта статья)

Создать компонент боковой панели, который:

  1. Выдвигается слева с плавной анимацией
  2. перечисляет содержимое из данных, находящихся внутри компонента
  3. Выдает данные из источника данных при нажатии на элемент.

Конечный результат ⤵

Часть 2: Связь родительского и дочернего компонентов с помощью Angular и Vanilla JS

  1. Чтение данных родительского компонента
  2. Выдача данных из данных родительского компонента, но вызванная событием из дочерней боковой панели
  3. Использовать эти данные для прокрутки к динамическим идентификаторам элементов в шаблоне

Пропустить вперед

  • Начальная настройка приложения (читайте, если вы новичок)
    • Создайте новое приложение
    • Добавить зависимости
    • Добавьте быстрый пакет SCSS
    • Добавление материальных иконок простым способом
  • Создание боковой панели
    • Создайте компонент
    • Создайте шаблон с помощью подстановочного содержимого и SCSS
    • Добавление и привязка данных компонента
    • Свойства и массивы данных
    • Доступ к данным и вложенным данным с помощью пространства имен
  • Передача данных компонента через событие щелчка
    • Добавьте функцию Click
    • Протестируйте ее в консоли
  • Добавление анимации
    • Использование AngularBrowserAnimations

Начальная настройка приложения


Прочитайте этот раздел, если вы новичок, в противном случае переходите к следующему разделу

Создайте новое приложение

Создайте приложение, выполнив $ ng new menu-demo --skip-tests в терминале. Выберите y для маршрутизации и выберите опцию SCSS в подсказке стиля.

Добавьте зависимости

В файл app.module.ts добавьте BrowserAnimationsModule и CommonModule, чтобы мы могли работать с директивами следующим образом:

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

// Add these ⤵
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    // And ⤵
    CommonModule,
    BrowserAnimationsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Войти в полноэкранный режим Выйти из полноэкранного режима

Добавление быстрого пакета SCSS

Чтобы оставаться на высоком уровне, я использую пакет @riapacheco/yutes на NPM.
После установки добавьте следующие импорты в ваш файл styles.scss.

@import '~@riapacheco/yutes/yutes.scss';
@import '~@riapacheco/yutes/colors.scss';
Вход в полноэкранный режим Выйти из полноэкранного режима

ИЛИ добавьте следующее в ваш файл angular.json:

"projects": [
  "menu-demo": [
    "build": [
      "options": [
        "styles": {
          "./node_modules/@riapacheco/yutes/yutes.scss",
          "./node_modules/@riapacheco/yutes/colors.scss"
        }
      ]
    ]
  ]
]
Войти в полноэкранный режим Выйти из полноэкранного режима

Добавление значков материалов простым способом

Мы хотим использовать иконки для кнопок «Открыть» и «Закрыть» меню, поэтому быстрый способ сделать это — использовать шрифт иконок Material от Google через href. Добавьте следующее внутри вашего <head> файла index.html:

<link href="https://fonts.googleapis.com/icon family=Material+Icons"rel="stylesheet">
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь вы можете добавить иконки, добавив класс material-icons к элементу, который называет тип иконки через свое содержимое, как показано здесь:

<i class="material-icons">search</i>
Вход в полноэкранный режим Выйти из полноэкранного режима

Будет показано вот так:


Создание боковой панели

Создайте компонент

Выполните команду $ ng g c components/sidebar в терминале, чтобы создать новый компонент.

Удалите все содержимое по умолчанию из файла app.component.html и замените его следующим селектором:

<app-sidebar></app-sidebar>
Вход в полноэкранный режим Выйти из полноэкранного режима

Построение шаблона с помощью содержимого-заполнителя и SCSS

Сначала мы добавим структуру боковой панели в шаблон таким образом, чтобы мы могли (позже с помощью SCSS) создать боковую панель, которая не перемещается, пока не сработает (position: absolute) и использует кнопку переключения иконок, которая появляется только при наведении курсора на меню. Мы добавляем условия к кнопке переключения тремя способами:

  1. Мы используем директиву Angular *ngIf, чтобы показать иконку, когда мы хотим, чтобы она была показана.
  2. Мы используем SCSS, чтобы установить opacity: 0 для иконок, пока не будет наведена курсор на общую область боковой панели.
  3. На самом якорном элементе .toggle-btn мы добавляем директиву (click), которая включает прямое поведение переключения с помощью showsSidebar = !showsSidebar.


Прочитайте комментарии ниже, чтобы понять, почему я добавил класс ‘close’ к первой иконке.

<nav class="sidebar">
  <div class="sidebar-content">
    <a
      (click)="showsSidebar = !showsSidebar"
      class="toggle-btn">
      <!--Shows Close Icon on button when sidebar is open and if hovering-->
      <!-- I've added an additional class 'close' to this so that I can differentiate between icons and keep the other `menu` icon visible when the menu is closed -->
      <span
        *ngIf="showsSidebar"
        class="material-icons close">
        close
      </span>

      <!-- Shows Menu Icon on button when sidebar is closed-->
      <span
        *ngIf="!showsSidebar"
        class="material-icons">
        menu
      </span>
    </a>

    <!-- This is a group of sections -->
    <div class="sections">
      <!-- This is a single section -->
      <div class="section">
        <!-- This is the title of a section -->
        <a href="" class="section-title">
          The Evolving State of SE
        </a>

        <!-- This is the list of items within a section -->
        <div class="section-content">
          <ul class="list-unstyled">
            <li>
              <a>
                Definitions and Terms
              </a>
            </li>

            <li>
              <a>
                Root Cause Analysis
              </a>
            </li>

            <li>
              <a>
                Industry and Government
              </a>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</nav>
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь мы добавим структуру absolute компонента с помощью SCSS, а также opacity: 0 к кнопке переключения. Она будет меняться на opacity: 1, когда пользователь наведет курсор на саму боковую панель:

@import '~@riapacheco/yutes/colors.scss';

nav, .sidebar {
  background-color: white;
  border-radius: 0px 6px 6px 0px;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 330px;
  box-shadow: 8px 8px 18px #00000030;

  .sidebar-content {
    width: 100%;

    // Full width button
    // Contains the icon, but uses flex-box to push to the right-side
    .toggle-btn {
      width: 100% !important;
      height: 1.5rem;
      padding-right: 0.4rem;

      display: flex;
      flex-flow: row nowrap;
      align-items: center;
      justify-content: flex-end;

      span {
        font-size: 0.99rem;
        // IF the button icon has a <span> AND a `.close` class,
        // THEN set its opacity to 0 [transparent]
        &.close {
          opacity: 0;
        }

        // IF the cursor hovers over the actual icon (not just the menu)
        // THEN change the icon color
        &:hover {
          color: $secondary-color;
        }
      }
    }
  }

  // IF the cursor is hovering over the sidebar menu,
  &:hover {
    .sidebar-content {
      .toggle-btn {
        // THEN change the toggle-btn's <span> element opacity: 0 to opacity: 1
        span {
          opacity: 1;
        }
      }
    }
  }
}

// All sections
.sections {
  margin-left: 2rem;
  margin-top: 2rem;

  .section{
    margin-bottom: 3rem;

    // Section Title
    .section-title {
      font-size: 0.88rem;
      line-height: 0.88rem;
      text-transform: uppercase;
      letter-spacing: 0.09rem;
      font-weight: 600;

      &:hover {
        color: $secondary-medium-color;
      }
    }

    // Listed items
    .section-content {
      ul {
        margin-top: 1rem;
        margin-bottom: 1rem;
        margin-left: 1rem;
        li {
          margin-bottom: 0.5rem;

          a:hover {
            color: $secondary-medium-color;
          }
        }
      }
    }
  }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь компонент выглядит следующим образом:


Добавление и привязка данных компонента

Сначала нам нужно будет добавить данные в компонент, чтобы:

  1. Мы понимаем форму данных (схему / иерархию).
  2. Убедиться, что применяемые стили имеют смысл для этой формы.

Свойства и массивы данных

Мы добавим свойство showsSidebar для управления начальным поведением боковой панели и установим его в true. Затем мы добавим массив данных объектов sections, который мы заполним прямо внутри компонента.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sidebar',
  templateUrl: './sidebar.component.html',
  styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent implements OnInit {

  // Toggles the sidebar from view
  showsSidebar = true;

  // Populated data so that we understand the shape / schema
  sections = [
    {
      sectionHeading: 'The Evolving State of SE',
      sectionTarget: 'theEvolvingStateOfSe',
      sectionContents: [
        {
          title: 'Definitions and Terms',
          target: 'definitionsAndTerms',
        },
        {
          title: 'Root Cause Analysis',
          target: 'rootCauseAnalysis',
        },
        {
          title: 'Industry and Government',
          target: 'industryAndGovernment',
        },
        {
          title: 'Engineering Education',
          target: 'engineeringEducation',
        },
        {
          title: 'Chapter Exercises',
          target: 'chapterExercises'
        }
      ]
    },
    {
      sectionHeading: 'Attributes and Properties',
      sectionTarget: 'attributesAndProperties',
      sectionContents: [
        {
          title: 'Definitions and Terms',
          target: 'defintionAndTerms',
        },
        {
          title: 'User Roles and Missions',
          target: 'userRolesAndMissions',
        },
        {
          title: 'Defining User Missions',
          target: 'definingUserMissions',
        },
        {
          title: 'Problem, Opportunity, Solution',
          target: 'problemOpportunitySolution',
        },
        {
          title: 'Spaces',
          target: 'spaces'
        },
      ]
    }
  ];

  constructor() { }

  ngOnInit(): void {
  }

}
Вход в полноэкранный режим Выход из полноэкранного режима

Важно помнить, что первый слой включает sectionTarget к типу string. И аналогично второй вложенный слой (список элементов под sectionContents) включает строку target. Они были добавлены в качестве ссылочной строки для последующей передачи.

Доступ к данным и вложенным данным с помощью пространства имен

Здесь мы используем директиву Angular *ngFor для привязки первого слоя данных массива к повторяющемуся элементу в шаблоне

<!-- Earlier Code -->

    <div class="sections">

      <!-- ------------------- This is where we bind the data -------------------- -->
      <div
        *ngFor="let section of sections;"
        class="section">

        <!--Section Title-->
        <a href="" class="section-title">
          {{ section.sectionHeading }}
        </a>
<!-- more code -->
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, когда мы создали пространство имен (например, section.sectionHeading) для доступа к данным. Мы можем использовать это пространство имен во второй раз для доступа внутри вложенного массива:

<!--Earlier Code-->

    <div class="sections">

      <!-- ------------------- This is where we bind the data -------------------- -->
      <div
        *ngFor="let section of sections;"
        class="section">

        <!--Section Title-->
        <a href="" class="section-title">
          {{ section.sectionHeading }}
        </a>

        <!-- ------------- This is where we access the nested content -------------- -->
        <div
          *ngFor="let sectionContent of section.sectionContents"
          class="section-content">
          <ul class="list-unstyled">
            <li>
              <a>
                {{ sectionContent.title }}
              </a>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</nav>
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь данные из компонента отражаются в представлении


Передача данных компонента через событие щелчка

Релевантные данные компонента, передаваемые через событие щелчка, очень важны, поскольку они позволяют использовать контекст. По сути, мы получаем возможность работать с данными, которые непосредственно связаны с элементом, на котором мы щелкнули (независимо от того, что шаблон динамически заполняется).
Это удобно для связи с родительским компонентом (часть 2 к этой статье) и для случаев, когда вы хотите выстроить передаваемые данные так, чтобы последующая функция (например, scrollTo()) могла поймать строку и использовать ее для прокрутки к нужному элементу в шаблоне, идентифицированному директивой elementRef.

Добавьте функцию Click

В шаблоне добавьте функцию onTargetContentClick(), которая принимает строку и событие, чтобы мы могли показать ее результат в консоли.

export class SidebarComponent implements OnInit {

  // More code

  onTargetContentClick(targetString: string, event: Event) {
    console.log(targetString);
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Помните, что у каждой «секции» есть строка «sectionTarget», а у каждой секцииContent есть строка «target». Добавьте функцию в шаблон следующим образом:

<!--Earlier Code-->

    <div class="sections">
      <div
        *ngFor="let section of sections;"
        class="section">

        <!-- Add the function to the Section title -->
        <a 
          (click)="onTargetContentClick(section.sectionTarget, $event)"
          class="section-title">
          {{ section.sectionHeading }}
        </a>


        <div
          *ngFor="let sectionContent of section.sectionContents"
          class="section-content">
          <ul class="list-unstyled">
            <li>
              <!--Add the function to every listed item's anchor element-->
              <a (click)="onTargetContentClick(sectionContent.target, $event)">
                {{ sectionContent.title }}
              </a>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</nav>
Войти в полноэкранный режим Выйти из полноэкранного режима

Протестируйте ее в консоли

Теперь вы можете открыть консоль Chrome devTools (нажав F12 на клавиатуре или command+alt+i, затем выбрав вкладку console) и нажать на каждый элемент, чтобы посмотреть, что появится:


Добавление анимации

Наконец, мы можем добавить анимацию, сначала добавив триггер анимации ([@triggerName]) через Angular Animations.
Добавляя триггер, за которым следует тернарный оператор, мы, по сути, говорим: если свойство isOpen истинно, то [@sidebarTrigger] будет использовать состояние open, определенное в файле компонента, иначе — close.

<!--Add an animation trigger followed by a ternary operator-->
<nav
  [@sidebarTrigger]="isOpen ? 'open' : 'close'"
  class="sidebar">
  <div class="sidebar-content">
    <a
      (click)="showsSidebar = !showsSidebar"
      class="toggle-btn">
      <span
        *ngIf="showsSidebar"
        class="material-icons close">
        close
      </span>
      <span
        *ngIf="!showsSidebar"
        class="material-icons"
        style="opacity: 1 !important;">
        menu
      </span>
    </a>
Вход в полноэкранный режим Выход из полноэкранного режима

Использование AngularBrowserAnimations

Теперь мы добавим анимации, импортировав декораторы из пакета и добавив массив в декоратор @Component следующим образом:

@Component({
  selector: 'app-sidebar',
  templateUrl: './sidebar.component.html',
  styleUrls: ['./sidebar.component.scss'],
  animations: [
    trigger('sidebarTrigger', [
      // To add a cool "enter" animation for the sidebar
      transition(':enter', [
        style({ transform: 'translateX(-100%)' }),
        animate('300ms ease-in', style({ transform: 'translateY(0%)' }))
      ]),

      // To define animations based on trigger actions
      state('open', style({ transform: 'translateX(0%)' })),
      state('close', style({ transform: 'translateX(-94%)' })),
      transition('open => close', [
        animate('300ms ease-in')
      ]),
      transition('close => open', [
        animate('300ms ease-out')
      ])
    ])
  ]
})
Вход в полноэкранный режим Выйти из полноэкранного режима

Завершенная автономная боковая панель

Теперь у нас есть автономная боковая панель, которая считывает данные из своего компонента и может передавать их через события щелчка.

Читайте «Связь родительского и дочернего компонентов с помощью Angular и Vanilla JS»: мы сделаем сайдбар многоразовым с помощью декораторов Angular @Input() и @Output(), используем EventEmitter для передачи данных родительского компонента через событие (click) дочернего компонента, и будем использовать эти же данные для прокрутки к динамическим идентификаторам в шаблоне!

Оставайтесь с нами.

Ri


Код для этой статьи можно найти в ветке Part-1 этого репозитория

Оцените статью
Procodings.ru
Добавить комментарий