- Цели
- Часть 1 (эта статья)
- Конечный результат ⤵
- Часть 2: Связь родительского и дочернего компонентов с помощью Angular и Vanilla JS
- Пропустить вперед
- Начальная настройка приложения
- Создайте новое приложение
- Добавьте зависимости
- Добавление быстрого пакета SCSS
- Добавление значков материалов простым способом
- Создание боковой панели
- Создайте компонент
- Построение шаблона с помощью содержимого-заполнителя и SCSS
- Добавление и привязка данных компонента
- Свойства и массивы данных
- Доступ к данным и вложенным данным с помощью пространства имен
- Передача данных компонента через событие щелчка
- Добавьте функцию Click
- Протестируйте ее в консоли
- Добавление анимации
- Использование AngularBrowserAnimations
- Завершенная автономная боковая панель
Цели
Часть 1 (эта статья)
Создать компонент боковой панели, который:
- Выдвигается слева с плавной анимацией
- перечисляет содержимое из данных, находящихся внутри компонента
- Выдает данные из источника данных при нажатии на элемент.
Конечный результат ⤵
Часть 2: Связь родительского и дочернего компонентов с помощью Angular и Vanilla JS
- Чтение данных родительского компонента
- Выдача данных из данных родительского компонента, но вызванная событием из дочерней боковой панели
- Использовать эти данные для прокрутки к динамическим идентификаторам элементов в шаблоне
Пропустить вперед
- Начальная настройка приложения (читайте, если вы новичок)
- Создайте новое приложение
- Добавить зависимости
- Добавьте быстрый пакет 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
) и использует кнопку переключения иконок, которая появляется только при наведении курсора на меню. Мы добавляем условия к кнопке переключения тремя способами:
- Мы используем директиву Angular
*ngIf
, чтобы показать иконку, когда мы хотим, чтобы она была показана. - Мы используем SCSS, чтобы установить
opacity: 0
для иконок, пока не будет наведена курсор на общую область боковой панели. - На самом якорном элементе
.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;
}
}
}
}
}
}
Теперь компонент выглядит следующим образом:
Добавление и привязка данных компонента
Сначала нам нужно будет добавить данные в компонент, чтобы:
- Мы понимаем форму данных (схему / иерархию).
- Убедиться, что применяемые стили имеют смысл для этой формы.
Свойства и массивы данных
Мы добавим свойство 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
этого репозитория