Ранее рассматривалось создание и настройка нового проекта Angular. В данной статье разберем базовую структуру.
Напомню, что цикл посвящен разработке веб-приложения для поиска авиабилетов и отелей. За основу взят проект от Альфа Тревел - travel.alfabank.ru
Сайт состоит из следующих блоков:
- Два экрана: мобильная и браузерная версии;
- 4 главных страницы, в которых меняется блок с формой;
- Технический раздел;
- Поиск билетов и отелей;
- Показ http ошибок - 404, 403 и 500.
Это позволяет нам выделить основные части:
- Базовый лейаут, содержащий шапку, контент и подвал;
- Единственная главная, которая бы отображала требуемую форму;
- Результаты поиска.
Настройка AppComponent
Изменим AppComponent
так, чтобы он выводил только routerOutlet
.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'baf-root',
standalone: true,
imports: [RouterOutlet],
template: '<router-outlet/>',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {}
Удалим неиспользуемые файлы: app.component.spec.ts
, app.component.scss
и app.component.html
.
Добавим конфигурацию для браузерной версии в app.config.browser.ts
:
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
import { appConfig } from './app.config';
const browserConfig: ApplicationConfig = {
providers: [provideAnimations()],
};
export const config = mergeApplicationConfig(appConfig, browserConfig);
И импортируем его в main.ts
:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.browser';
bootstrapApplication(AppComponent, config).catch((err) => console.error(err));
Добавление hammerjs
Для мобильной версии нам нужно работать с тачами и сваймами, поэтому используем hammerjs
Установим зависимость:
yarn add -D hammerjs @types/hammerjs
Подключим анимацию и hammerjs
в браузере:
import 'hammerjs';
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
import { appConfig } from './app.config';
const browserConfig: ApplicationConfig = {
providers: [provideAnimations()],
};
export const config = mergeApplicationConfig(appConfig, browserConfig);
Необходимо задать конфигурацию для hammerjs
.
Создаем новую папку core
, в которой будем хранить все, что является неотъемлемой частью проекта.
mkdir src/app/core
mkdir src/app/core/lib
echo >src/app/core/index.ts
mkdir src/app/core/lib/hammer
echo >src/app/core/lib/hammer/hammer.ts
В hammer.ts
указываем конфиг:
import { EnvironmentProviders, importProvidersFrom, Injectable, Provider } from '@angular/core';
import { HAMMER_GESTURE_CONFIG, HammerGestureConfig, HammerModule } from '@angular/platform-browser';
@Injectable()
export class HammerConfig extends HammerGestureConfig {
override overrides = {
swipe: { velocity: 0.4, threshold: 20 },
pinch: { enable: false },
rotate: { enable: false },
};
}
export function provideHammer(): (Provider | EnvironmentProviders)[] {
return [
importProvidersFrom(HammerModule),
{
provide: HAMMER_GESTURE_CONFIG,
useClass: HammerConfig,
},
];
}
Экспортируем в src/app/сore/index.ts
:
export * from './lib/hammer/hammer';
Для быстрого обращения добавим алиас в tsconfig.json
:
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"],
"baseUrl": ".",
"paths": {
"@baf/core": ["src/app/core/index.ts"]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
Отмечу, что нужно еще указать
baseUrl
.
Подключим в браузерной версии:
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideHammer } from '@baf/core';
import { appConfig } from './app.config';
const browserConfig: ApplicationConfig = {
providers: [provideAnimations(), provideHammer()],
};
export const config = mergeApplicationConfig(appConfig, browserConfig);
Создание лейаута
Лейаут является общим для всего веб-приложения. Добавим новую папку UI, в которой будем хранить компоненты.
mkdir src/app/ui
mkdir src/app/ui/layout/lib
echo >src/app/ui/layout/index.ts
Запустим команду:
yarn ng g c layout
Перенесем содержимое в src/app/ui/layout/lib
.
Видим, что все создается без нужных нам атрибутов и с файлами тестов:
import { Component } from '@angular/core';
@Component({
selector: 'baf-layout',
standalone: true,
imports: [],
templateUrl: './layout.component.html',
styleUrl: './layout.component.scss'
})
export class LayoutComponent {}
В angular.json
укажем свойства:
{
"@schematics/angular:component": {
"style": "scss",
"changeDetection": "OnPush",
"skipTests": true
}
}
Отредактируем LayoutComponent
:
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'baf-results',
standalone: true,
imports: [RouterOutlet],
templateUrl: './layout.component.html',
styleUrl: './layout.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LayoutComponent {}
Добавим шапку, контент и подвал:
<router-outlet name="top" />
<header>
<router-outlet name="header" />
</header>
<main>
<router-outlet name="main-top" />
<router-outlet />
<router-outlet name="main-bottom" />
</main>
<footer>
<router-outlet name="footer" />
</footer>
<router-outlet name="bottom" />
Немного стилей:
:host {
display: flex;
min-height: 100vh;
flex-direction: column;
}
header,
footer {
flex-shrink: 0;
}
main {
flex-grow: 1;
overflow-x: hidden;
}
Экспортируем компонент в src/app/ui/layout/index.ts
:
export * from './lib/layout.component';
И пропишем алиас в tsconfig.json
:
{
"paths": {
"@baf/core": ["src/app/core/index.ts"],
"@baf/ui/layout": ["src/app/ui/layout/index.ts"]
}
}
Сброс стилей
Прежде чем вывести лейаут, нужно настроить стили в приложении.
Для сброса стандартного оформления в браузере достаточно следующего:
/* You can add global styles to this file, and also import other style files */
@use '@angular/cdk' as cdk;
// Hack for global CDK dialogs styles
@include cdk.overlay();
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
blockquote {
margin: 0;
padding: 1rem;
}
h1 {
margin-block-start: 1.45rem;
margin-block-end: 1.45rem;
}
h2 {
margin-block-start: 1.25rem;
margin-block-end: 1.25rem;
}
h3 {
margin-block-start: 1.175rem;
margin-block-end: 1.175rem;
}
h4 {
margin-block-start: 1.15rem;
margin-block-end: 1.15rem;
}
figure {
margin: 0;
}
p {
margin-block-start: 1rem;
margin-block-end: 1rem;
}
ul[role='list'],
ol[role='list'] {
list-style: none;
}
body {
margin: 0;
min-height: 100vh;
line-height: 1.5;
font-family:
Arial,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
sans-serif;
font-size: 16px;
}
h1,
h2,
h3,
h4,
button,
input,
label {
line-height: 1.1;
}
h1,
h2,
h3,
h4 {
text-wrap: balance;
}
a:not([class]) {
text-decoration-skip-ink: auto;
color: currentColor;
}
img,
picture {
max-width: 100%;
display: block;
}
input,
button,
textarea,
select {
font: inherit;
}
textarea:not([rows]) {
min-height: 10rem;
}
:target {
scroll-margin-block: 5ex;
}
Reset разместим в styles.scss
.
Отредактируем index.html
:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>BuyAndFly</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet" />
<meta name="description" content="Example Angular 18 application for search for cheap flights and hotels." />
<link rel="preconnect" href="https://photo.hotellook.com" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#172659" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="msapplication-TileColor" content="#172659" />
<meta name="theme-color" content="#ffffff" />
</head>
<body>
<baf-root></baf-root>
</body>
</html>
В public
добавим сгенерированный favicons
, а также другие файлы:
browserconfig.xml:
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/favicons/mstile-150x150.png"/>
<TileColor>#172659</TileColor>
</tile>
</msapplication>
</browserconfig>
site.webmanifest:
{
"name": "Buy & Fly",
"short_name": "Buy & Fly",
"icons": [
{
"src": "/favicons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone",
"start_url": ".",
"description": "Search for cheap flights and hotels.",
"categories": ["travel", "education"],
"screenshots": [
{
"src": "screenshot.webp",
"sizes": "1280x720",
"type": "image/webp"
}
]
}
robots.txt:
User-agent: *
Disallow: /api
User-agent: Yandex
Disallow: /api
Clean-param: bonus&utm_source&utm_medium&utm_campaign&utm_term&utm_content&click_id&appstore&platform
Host: https://buy-and-fly.fafn.ru
Sitemap: https://buy-and-fly.fafn.ru/sitemap.xml
В конце используем layout
в src/app/app.routes.ts
:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () => import('@baf/ui/layout').then((m) => m.LayoutComponent),
children: [],
},
];
Запустим приложение:
yarn serve
Увидим белый экран :)
Добавление шапки и футера
Создадим шапку и подвал:
yarn ng g c header
yarn ng g c footer
Перенесем в ui/layout
и экспортируем:
export * from './lib/footer/footer.component';
export * from './lib/header/header.component';
export * from './lib/layout.component';
Подключим их в приложении:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () => import('@baf/ui/layout').then((m) => m.LayoutComponent),
children: [
{
path: '',
loadComponent: () => import('@baf/ui/layout').then((m) => m.HeaderComponent),
outlet: 'header',
},
{
path: '',
loadComponent: () => import('@baf/ui/layout').then((m) => m.FooterComponent),
outlet: 'footer',
},
],
},
];
Запустим проект:
yarn serve
Видим созданные компоненты.
В следующей статье добавим core
сервисы и интерфейсы.
Ссылки
Все исходники находятся на github, в репозитории - github.com/Fafnur/buy-and-fly
Демо можно посмотреть здесь - buy-and-fly.fafn.ru/
Top comments (0)