Эта статья — перевод оригинальной статьи "Angular v15 is now available!"
Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
За последний год мы удалили устаревший компилятор Angular и пайплайн рендеринга, что позволило разработать ряд улучшений для разработчиков за последние пару месяцев. Angular v15 является кульминацией этого с десятками усовершенствований, которые обеспечивают лучший опыт разработчиков и производительность.
Standalone API теперь вышли из developer preview!
В v14 мы представили новые Standalone API, которые позволяют разработчикам создавать приложения без использования NgModules. Мы рады сообщить, что эти API вышли из предварительной версии для разработчиков и теперь являются частью стабильной поверхности API. С этого момента мы будем развивать их постепенно, следуя семантическим версиям.
В рамках подготовки Standalone API к выпуску мы обеспечили работу standalone компонентов в Angular, и теперь они полностью работают в HttpClient, Angular Elements, маршрутизаторе и многом другом.
Standalone API позволяют загружать приложение с помощью одного компонента:
import {bootstrapApplication} from '@angular/platform-browser';
import {ImageGridComponent} from'./image-grid';
@Component({
standalone: true,
selector: 'photo-gallery',
imports: [ImageGridComponent],
template: `
… <image-grid [images]="imageList"></image-grid>
`,
})
export class PhotoGalleryComponent {
// component logic
}
bootstrapApplication(PhotoGalleryComponent);
Древовидные Router и HttpClient standalone API
Вы можете создать приложение с несколькими маршрутами, используя новые standalone API-интерфейсы маршрутизатора! Чтобы объявить корневой маршрут, вы можете использовать следующее:
export const appRoutes: Routes = [{
path: 'lazy',
loadChildren: () => import('./lazy/lazy.routes')
.then(routes => routes.lazyRoutes)
}];
Где lazyRoutes
объявлены в:
import {Routes} from '@angular/router';
import {LazyComponent} from './lazy.component';
export const lazyRoutes: Routes = [{path: '', component: LazyComponent}];
и, наконец, зарегистрируйте appRoutes
в вызове bootstrapApplication
:
bootstrapApplication(AppComponent, {
providers: [
provideRouter(appRoutes)
]
});
Еще одним преимуществом API-интерфейса providerRouter
является то, что он поддерживает древовидную структуру! Сборщики могут удалить неиспользуемые функции маршрутизатора во время сборки. При тестировании нового API мы обнаружили, что удаление этих неиспользуемых функций из пакета привело к уменьшению размера кода маршрутизатора в bundle приложения на 11%.
API композиции директив
API композиции директив выводит переиспользование кода на новый уровень! Эта функция была вдохновлена самым популярным запросом на GitHub, в котором запрашивалась функциональность для добавления директив к элементу хоста.
API композиции директив позволяет разработчикам улучшать элементы хоста с помощью директив и предоставляет Angular мощную стратегию повторного использования кода, которая возможна только благодаря нашему компилятору. API композиции директив работает только с standalone директивами.
Давайте посмотрим на быстрый пример:
@Component({
selector: 'mat-menu',
hostDirectives: [HasColor, {
directive: CdkMenu,
inputs: ['cdkMenuDisabled: disabled'],
outputs: ['cdkMenuClosed: closed']
}]
})
class MatMenu {}
В приведенном выше фрагменте кода мы улучшаем MatMenu
двумя директивами: HasColor
и CdkMenu
. MatMenu
повторно использует все inputs, outputs и связанную логику с HasColor
и только логику и выбранные входы из CdkMenu
.
Этот метод может напомнить вам о множественном наследовании или трейтах в некоторых языках программирования, с той разницей, что у нас есть механизм разрешения конфликтов имен, и он применим к примитивам пользовательского интерфейса.
Директива Image теперь является стабильной!
Мы анонсировали предварительную версию директивы Image для разработчиков Angular, которую мы разработали в сотрудничестве с Chrome Aurora в версии 14.2.
Мы рады сообщить, что теперь он стабилен! Компания Land’s End экспериментировала с этой функцией и наблюдала 75-процентное улучшение LCP в испытаниях lighthouse lab.
Версия v15 также включает несколько новых функций для директивы Image:
Автоматическая генерация srcset: директива гарантирует, что будет запрошено изображение соответствующего размера, генерируя для вас атрибут
srcset
. Это может сократить время загрузки ваших изображений.Режим заполнения [экспериментальный]: в этом режиме изображение заполняет свой родительский контейнер, устраняя необходимость объявлять ширину и высоту изображения. Это удобный инструмент, если вы не знаете размеров своих изображений или хотите перенести фоновые изображения CSS для использования директивы.
Вы можете использовать директиву NgOptimizedImage непосредственно в вашем компоненте или NgModule
:
import { NgOptimizedImage } from '@angular/common';
// Include it into the necessary NgModule
@NgModule({
imports: [NgOptimizedImage],
})
class AppModule {}
// ... or a standalone Component
@Component({
standalone: true
imports: [NgOptimizedImage],
})
class MyStandaloneComponent {}
Чтобы использовать его в компоненте, просто замените атрибут src
изображения на ngSrc
и убедитесь, что вы указали атрибут приоритета для ваших изображений LCP.
Более подробную информацию вы можете найти в нашей документации.
Функциональные guards маршрутизатора
Вместе с API-интерфейсами автономных маршрутизаторов с поддержкой деревьев мы работали над сокращением шаблонного кода в guards. Давайте рассмотрим пример, в котором мы определяем защиту, которая проверяет, вошел ли пользователь в систему:
@Injectable({ providedIn: 'root' })
export class MyGuardWithDependency implements CanActivate {
constructor(private loginService: LoginService) {}
canActivate() {
return this.loginService.isLoggedIn();
}
}
const route = {
path: 'somePath',
canActivate: [MyGuardWithDependency]
};
LoginService
реализует большую часть логики, а в guard мы вызываем только isLoggedIn()
. Несмотря на то, что защита довольно проста, у нас много шаблонного кода.
С помощью новых функциональных средств защиты маршрутизатора вы можете реорганизовать этот код до:
const route = {
path: 'admin',
canActivate: [() => inject(LoginService).isLoggedIn()]
};
Мы описали весь guard в его объявлении. Функциональные guards также компонуемы — вы можете создавать фабричные функции, которые принимают конфигурацию и возвращают guard функцию или resolver функцию. Вы можете найти пример последовательного запуска Route Guards на GitHub.
Маршрутизатор не оборачивает default импорт
Чтобы упростить маршрутизатор и еще больше сократить шаблоны, маршрутизатор теперь автоматически разворачивает экспорт по умолчанию при lazy загрузке.
Предположим, у вас есть следующий LazyComponent
:
@Component({
standalone: true,
template: '...'
})
export default class LazyComponent { ... }
До этого изменения для ленивой загрузки автономного компонента необходимо было:
{
path: 'lazy',
loadComponent: () => import('./lazy-file').then(m => m.LazyComponent),
}
Теперь маршрутизатор будет искать экспорт по умолчанию и, если найдет, использовать его автоматически, что упрощает объявление маршрута до:
{
path: 'lazy',
loadComponent: () => import('./lazy-file'),
}
Улучшенная трассировка стека
Мы получаем много информации из наших ежегодных опросов разработчиков, поэтому мы хотим поблагодарить вас за то, что вы нашли время, чтобы поделиться своими мыслями! Углубившись в трудности, с которыми сталкиваются разработчики при отладке, мы обнаружили, что сообщения об ошибках нуждаются в некотором улучшении.
Проблемы отладки для разработчиков Angular
Мы сотрудничали с Chrome DevTools, чтобы исправить это! Давайте посмотрим на образец трассировки стека, который вы можете использовать для работы с приложением Angular:
ERROR Error: Uncaught (in promise): Error
Error
at app.component.ts:18:11
at Generator.next (<anonymous>)
at asyncGeneratorStep (asyncToGenerator.js:3:1)
at _next (asyncToGenerator.js:25:1)
at _ZoneDelegate.invoke (zone.js:372:26)
at Object.onInvoke (core.mjs:26378:33)
at _ZoneDelegate.invoke (zone.js:371:52)
at Zone.run (zone.js:134:43)
at zone.js:1275:36
at _ZoneDelegate.invokeTask (zone.js:406:31)
at resolvePromise (zone.js:1211:31)
at zone.js:1118:17
at zone.js:1134:33
Этот фрагмент страдает от двух основных проблем:
Есть только одна строка, соответствующая коду, созданному разработчиком. Все остальное исходит от сторонних зависимостей (фреймворк Angular, Zone.js, RxJS).
Нет информации о том, какое взаимодействие с пользователем вызвало ошибку
Команда Chrome DevTools создала механизм игнорирования скриптов, исходящих из node_modules, путем аннотирования исходных карт через Angular CLI. Мы также совместно работали над API тегирования асинхронного стека, который позволял нам объединять независимые запланированные асинхронные задачи в единую трассировку стека. Jia Li интегрировала Zone.js с API асинхронной маркировки стека, что позволило нам предоставлять связанные трассировки стека.
Эти два изменения значительно улучшают трассировку стека, которую разработчики видят в Chrome DevTools:
ERROR Error: Uncaught (in promise): Error
Error
at app.component.ts:18:11
at fetch (async)
at (anonymous) (app.component.ts:4)
at request (app.component.ts:4)
at (anonymous) (app.component.ts:17)
at submit (app.component.ts:15)
at AppComponent_click_3_listener (app.component.html:4)
Здесь вы можете проследить выполнение от нажатия кнопки в AppComponent
до ошибки. Подробнее об улучшениях можно прочитать здесь.
Выпуск компонентов на основе MDC до стабильной версии
Мы рады сообщить, что рефакторинг компонентов материалов Angular на основе Material Design Components for Web (MDC) завершен! Это изменение позволяет Angular еще больше приблизиться к спецификации Material Design, повторно использовать код из примитивов, разработанных командой Material Design, и позволить нам принять Material 3 после того, как мы завершим работу над токенами стиля.
Для многих компонентов мы обновили стили и структуру DOM, а другие переписали с нуля. Мы сохранили большинство API-интерфейсов TypeScript и селекторов компонентов/директив для новых компонентов, идентичных старой реализации.
Мы перенесли тысячи проектов Google, что позволило нам упростить путь внешней миграции и задокументировать полный список изменений во всех компонентах.
Из-за новых DOM и CSS вы, вероятно, обнаружите, что некоторые стили в вашем приложении необходимо настроить, особенно если ваш CSS переопределяет стили внутренних элементов любого из перенесенных компонентов.
Старая реализация каждого нового компонента теперь является deprecated, но все еще доступна из «deprecated» импорта. Например, вы можете импортировать старую реализацию mat-button
, импортировав устаревший модуль кнопки.
import {MatLegacyButtonModule} from '@angular/material/legacy-button';
Посетите руководство по миграции для получения дополнительной информации.
Мы переместили многие компоненты для использования токенов дизайна и CSS переменных под капотом, что обеспечит плавный путь для приложений, чтобы применить стили компонентов Material 3.
Больше улучшений в компонентах
Мы решили четвертую по количеству голосов проблему — поддержку выбора диапазона в ползунке.
Чтобы получить диапазон ввода, используйте:
<mat-slider>
<input matSliderStartThumb>
<input matSliderEndThumb>
</mat-slider>
Кроме того, все компоненты теперь имеют API для настройки density, что решило еще одну популярную проблему GitHub.
Теперь вы можете указать density по умолчанию для всех ваших компонентов, настроив тему:
@use '@angular/material' as mat;
$theme: mat.define-light-theme((
color: (
primary: mat.define-palette(mat.$red-palette),
accent: mat.define-palette(mat.$blue-palette),
),
typography: mat.define-typography-config(),
density: -2,
));
@include mat.all-component-themes($theme);
Новые версии компонентов включают в себя широкий спектр улучшений специальных возможностей, в том числе улучшенные коэффициенты контрастности, увеличенные размеры объектов касания и улучшенную семантику ARIA.
CDK Listbox
The Component Dev Kit (CDK) предлагает набор примитивов поведения для создания компонентов пользовательского интерфейса. В v15 мы представили еще один примитив, который вы можете настроить для своего варианта использования — CDK listbox:
Модуль angular/cdk/listbox
предоставляет директивы, помогающие создавать настраиваемые взаимодействия со списком на основе шаблона списка WAI ARIA.
Улучшения в экспериментальной поддержке esbuild
В версии 14 мы объявили об экспериментальной поддержке esbuild в ng build
, чтобы ускорить сборку и упростить pipeline.
В v15 теперь у нас есть экспериментальный Sass, шаблон SVG, замена файлов и ng build --watchsupport
! Пожалуйста, попробуйте esbuild, обновив angular.json
ваших сборщиков из:
"builder": "@angular-devkit/build-angular:browser"
в:
"builder": "@angular-devkit/build-angular:browser-esbuild"
Если у вас возникнут какие-либо проблемы с вашими производственными сборками, сообщите нам об этом, отправив сообщение о проблеме на GitHub.
Автоматический импорт в языковой службе
Языковая служба теперь может автоматически импортировать компоненты, которые вы используете в шаблоне, но не добавили в автономный компонент или NgModule.
Улучшения CLI
В Angular CLI мы представили поддержку стабильных standalone API. Теперь вы можете сгенерировать новый standalone компонент с помощью ng g component --standalone
.
Мы также стремимся упростить вывод ng new
. В качестве первого шага мы уменьшаем конфигурацию, удаляя test.ts
, polyfills.ts
и environments
. Теперь вы можете указать свои полифиллы непосредственно в angular.json
в полеpolyfills
:
"polyfills": [
"zone.js"
]
Чтобы еще больше снизить нагрузку на конфигурацию, мы теперь используем .browserlist
, чтобы вы могли определить целевую версию ECMAScript.
Основные моменты вклада сообщества
Мы рады поделиться тем, что с момента выпуска v14 мы получили вклад от более чем 210 человек по фреймворку, компонентам и CLI! В этом разделе я хотел бы выделить два из них.
Предоставьте возможность настроить параметры по умолчанию для DatePipe.
Эта функция Matthias Weiß позволяет глобально изменить конфигурацию форматирования по умолчанию для DatePipe
. Вот пример с новым API bootstrapApplication
:
bootstrapApplication(AppComponent, {
providers: [
{
provide: DATE_PIPE_DEFAULT_OPTIONS,
useValue: { dateFormat: 'shortDate' }
}
]
});
Приведенная выше конфигурация активирует формат ShortDate
для всех мест, где вы используете DatePipe
в своем приложении.
Добавить тег предварительной загрузки <link> для приоритетных изображений во время SSR.
Чтобы обеспечить максимально быструю загрузку приоритетных изображений, Jay Bell добавил в директиву изображения функцию включения для них тега <link rel="preload">
при использовании Angular Universal.
С вашей стороны не требуется никаких действий, если вы уже включили директиву изображения. Если вы указали изображение как приоритетное, директива автоматически предварительно загрузит его.
Deprecations
Большие релизы позволяют нам развивать платформу в сторону простоты, лучшего взаимодействия с разработчиками и согласования с веб-платформой.
Проанализировав тысячи проектов в Google, мы обнаружили несколько редко используемых паттернов, которые в большинстве случаев используются не по назначению. В результате мы объявляем устаревшим параметр providedIn : any
— это параметр, который имеет очень ограниченное применение, за исключением нескольких экзотических случаев внутри фреймворка.
Кроме того, мы отказываемся от использования providedIn: NgModule
. Он не имеет широкого использования и в большинстве случаев используется неправильно, в обстоятельствах, когда вы должны предпочесть предоставление In: 'root'. Если вам действительно нужно ограничить поставщиков определенным NgModule
, вместо этого используйте NgModule.providers
.
С развитием макета в CSS команда прекратит публиковать новые выпуски angular/flex-layout
. Мы продолжим выпускать исправления безопасности и совместимости браузера в следующем году. Вы можете узнать больше об этом в первом сообщении в блоге из нашей серии «Современный CSS».
В восторге от того, что будет дальше!
Запуск Ivy в 2020 году позволил внести множество улучшений по всем направлениям, которые вы уже можете найти. Необязательный NgModules — отличный пример. Это помогает сократить количество концепций, с которыми новички должны иметь дело в рамках своего важного пути обучения, а также поддерживает расширенные функции, такие как API композиции директив с помощью автономных директив.
Далее мы занимаемся улучшениями в нашем конвейере рендеринга на стороне сервера и реактивностью, одновременно улучшая качество жизни по всем направлениям!
Не терпится поделиться с вами тем, что будет дальше!