Что нового в Angular 17?

С версией 17 Angular Renaissance развивается. Новый синтаксис потока управления упрощает структуру шаблонов. Новый алгоритм согласования, используемый вместе с новым потоком управления, значительно повышает производительность повторного рендеринга.

Благодаря отложенной загрузке менее важные области страницы могут быть загружены позже. Это ускоряет начальную загрузку страницы. При использовании esbuild операторы ng build и ng submit выполняются заметно быстрее, таким образом, это значительно ускоряет сборку. Кроме того, CLI теперь напрямую поддерживает SSR и предварительный рендеринг. Давайте все это рассмотрим более детально.

Новый синтаксис для потока управления (Control Flow) в шаблонах

С самого начала Angular использовал структурные директивы, такие как *ngIf или *ngFor , для потока управления. Поскольку поток управления в любом случае необходимо пересмотреть, чтобы обеспечить предусмотренное детальное обнаружение изменений и, в конечном итоге, отказаться от зон, команда Angular решила поставить его на новую основу. Результатом является новый встроенный поток управления, который четко выделяется на визуализированной разметке:

@for (product of products(); track product.id) {
    <div class="card">
        <h2 class="card-title">{{product.productName}}</h2>
        […]
    </div>
}
@empty {
    <p class="text-lg">No Products found!</p>
}

Здесь стоит отметить новый блок @empty , который Angular отображает, когда итерируемая коллекция пуста.

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

Обязательное выражение отслеживания позволяет Angular идентифицировать отдельные элементы, которые были перемещены внутри итерированной коллекции. Это позволяет Angular (точнее, новому алгоритму согласования Angular) радикально сократить усилия по рендерингу и повторно использовать существующие узлы DOM. При итерации коллекций примитивных типов, например, массивов с числами или строками, track может указывать на псевдопеременную $index согласно информации от команды Angular:

@for (group of groups(); track $index) {
    <a (click)="groupSelected(group)">{{group}}</a>
    @if (!$last) { 
        <span class="mr-5 ml-5">|</span> 
    }
}

Помимо $index , другие значения, известные из *ngFor , также доступны через псевдопеременные: $count , $first , $last , $even , $odd . При необходимости их значения также можно сохранить в переменных шаблона:

@for (group of groups(); track $index; let isLast = $last) {
    <a (click)="groupSelected(group)">{{group}}</a>
    @if (!isLast) { 
        <span class="mr-5 ml-5">|</span> 
    }
}

Новый @if упрощает формулировку ветвей else / else-if :

@if (product().discountedPrice && product().discountMinCount) {
    […]
}
@else if (product().discountedPrice && !product().discountMinCount) {
    […]
}
@else {
    […]
}

Кроме того, разные случаи также можно различать с помощью @switch :

@switch (mode) {
    @case ('full') {
      […]
    }
    @case ('small') {
      […]
    }
    @default {
      […]
    }
}

В отличие от ngSwitch и *ngSwitchCase , новый синтаксис типобезопасен. В примере, показанном выше, отдельные блоки @case должны иметь строковые значения, поскольку переменная режима , передаваемая в @switch , также является строкой.

Новый синтаксис потока управления уменьшает необходимость использования структурных директив, которые являются мощными, но иногда излишне сложными. Тем не менее, структура будет продолжать поддерживать структурные директивы. С одной стороны, у него есть несколько реальных вариантов использования, а с другой стороны, несмотря на множество интересных инноваций, фреймворк необходимо сделать обратно совместимым.

Автоматический переход на встроенный поток управления

Если вы хотите автоматически перенести свой программный код на новый синтаксис потока управления, вы найдете схему для этого в файле @angular/core package:

ng g @angular/core:control-flow

Отложенная загрузка

Как правило, не все области страницы одинаково важны. Страница продукта — это прежде всего сам продукт. Предложения по аналогичным продуктам вторичны. Однако ситуация внезапно меняется, как только пользователь прокручивает предложения продуктов в видимую область окна браузера, так называемую область просмотра.

Для особенно критичных к производительности веб-приложений, таких как интернет-магазины, имеет смысл отложить загрузку менее важных частей страницы. Это означает, что действительно важные элементы доступны быстрее. До сих пор любому, кто хотел реализовать эту идею в Angular, приходилось делать это вручную. Angular 17 также значительно упрощает эту задачу благодаря новому блоку @defer :

@defer (on viewport) {
    <app-recommentations [productGroup]="product().productGroup">
        </app-recommentations>
}
@placeholder {
    <app-ghost-products></app-ghost-products>
}

Использование @defer задерживает загрузку вложенной части страницы до тех пор, пока не произойдет определенное событие. В качестве замены он представляет заполнитель, указанный в @placeholder . В используемом здесь демонстрационном приложении сначала представлены призрачные элементы для предложений продуктов:

Призрачные элементы в качестве заполнителей

После загрузки @defer заменяет призрачные элементы реальными предложениями:

@defer заменяет заполнитель для лениво загружаемого компонента.

В обсуждаемом примере используется событие on viewport . Это происходит, когда заполнитель прокручивается в поле зрения. Помимо этого мероприятия, есть еще несколько вариантов:

По умолчанию в области просмотра , при взаимодействии и при наведении принудительно указывается блок @placeholder . Альтернативно, они также могут ссылаться на другие части страницы, на которые можно ссылаться через переменную шаблона:

<h1 #recommentations>Recommentations</h1> 
@defer (on viewport(recommentations)) { 
    <app-recommentations […] />
} 

Кроме того, @defer можно указать предварительно загрузить пакет раньше. Как и в случае с предварительной загрузкой маршрутов, этот подход гарантирует, что пакеты будут доступны, как только они вам понадобятся:

@defer(on viewport; prefetch on immediate) { […] }

Помимо @placeholder , @defer также предлагает два других блока: @loading и @error . Angular отображает первый во время загрузки пакета; последний отображается в случае ошибки. Чтобы избежать мерцания, @placeholder и @loading можно настроить с минимальной длительностью отображения. Минимальное свойство устанавливает желаемое значение :

@defer ( […] ) { 
    […] 
} 
@loading (after 150ms; minimum 150ms) { 
    […] 
} 
@placeholder (minimum 150ms) { 
    […] 
}

Свойство after также указывает, что индикатор загрузки должен отображаться только в том случае, если загрузка занимает более 150 мс.

Повышайте производительность с помощью esbuild

Первоначально Angular CLI использовал веб-пакет для создания пакетов. Однако в настоящее время веб-пакету бросают вызов новые инструменты, которые проще в использовании и намного быстрее. esbuild — один из таких инструментов, который загружается более 20 000 раз в неделю и имеет замечательное распространение.

Команда CLI работала над интеграцией esbuild для нескольких выпусков. В Angular 16 эта интеграция уже была включена в предварительную версию для разработчиков. Начиная с Angular 17, эта реализация стабильна и используется по умолчанию для новых проектов Angular через Среду разработки приложений, описанную ниже.

Для существующих проектов стоит рассмотреть возможность перехода на esbuild. Для этого обновите запись компоновщика в angular.json :

"builder" : "@angular-devkit/build-angular:browser-esbuild"

Другими словами: -esbuild необходимо добавить в конце. В большинстве случаев ngserve и ngbuild должны вести себя как обычно, но работать намного быстрее. Первый использует сервер разработки vite для ускорения работы, создавая пакеты npm только при необходимости. Кроме того, команда CLI внедрила несколько дополнительных оптимизаций производительности.

Вызов ng build также значительно ускоряется при использовании esbuild. В качестве диапазона часто называют коэффициент от 2 до 4.

SSR без усилий с новой средой разработки приложений

Поддержка рендеринга на стороне сервера (SSR) также была значительно упрощена в Angular 17. При создании нового проекта с помощью ng new теперь доступен переключатель --ssr . Если это не используется, CLI спрашивает, следует ли настроить SSR:

ng new настраивает SSR при желании

Чтобы включить SSR позже, все, что вам нужно сделать, это добавить пакет @angular/ssr :

ng add @angular/ssr

Область @angular ясно показывает, что этот пакет создан непосредственно командой Angular. Это преемник проекта сообщества Angular Universal. Чтобы напрямую учитывать SSR во время ng build и ng submit , команда CLI предоставила новый сборщик. Этот так называемый сборщик приложений использует упомянутую выше интеграцию esbuild и создает пакеты, которые можно использовать как в браузере, так и на стороне сервера.

Вызов ngserve также запускает сервер разработки, который выполняет рендеринг на стороне сервера и доставляет пакеты для работы в браузере. Вызов ng build --ssr также заботится о пакетах для обоих миров, а также создает простой сервер на базе Node.js, исходный код которого использует упомянутые выше схемы.

Если вы не можете или не хотите запускать сервер Node.js, вы можете использовать ng build --prerender для предварительной обработки отдельных маршрутов приложения во время сборки.

Дальнейшие улучшения

В дополнение к нововведениям, обсуждавшимся до сих пор, Angular 17 также предлагает множество других функций округления:

Маршрутизатор теперь поддерживает API View Transitions . Этот API, предлагаемый некоторыми браузерами, позволяет анимировать переходы с помощью CSS, например, при переходе с одного маршрута на другой. Эту дополнительную функцию необходимо активировать при настройке маршрутизатора с помощью функции withViewTransitions :

export const appConfig: ApplicationConfig = {
    providers: [
        provideRouter(
            routes,
            withComponentInputBinding(),

            // Activating View Transitions API:
            withViewTransitions(),
        ), 
        [...]
    ]
};

[...]

bootstrapApplication(AppComponent, appConfig)
    .catch((err) => console.error(err));

В целях демонстрации в примере используются CSS-анимации, взятые с этой страницы документации .

Сигналы, представленные в версии 16 в качестве предварительной версии для разработчиков, теперь стабильны. Важным изменением по сравнению с версией 16 является то, что сигналы теперь по умолчанию предназначены для использования с неизменяемыми объектами. Это облегчает Angular поиск изменений в структурах данных, управляемых с помощью Signals. Для обновления сигналов вы можете использовать метод set , который присваивает новое значение, или метод update , который сопоставляет существующее значение с новым. Метод mutate был удален, тем более что он не соответствует семантике Immutables.

Хотя Signals сейчас вышла из предварительной версии для разработчиков, effect-Method все еще находится в предварительной версии для разработчиков, поскольку еще есть некоторые случаи, которые команда Angular хочет изучить более подробно.

Изменение сигнала с привязкой к данным заставляет Angular просто помечать компоненты, на которые непосредственно влияет это изменение (= компоненты, которые привязывают данные к этому сигналу), как грязные. Это отличается от традиционного поведения, которое также отмечает всех родителей как грязных. При использовании вместе с OnPush, это повышает производительность. Кроме того, это первый шаг на пути к более детальному обнаружению изменений.

Теперь имеется диагностика, которая выдает предупреждение, если вызов метода получения был забыт при чтении сигналов в шаблонах (например, {{ products }} вместо {{products() }} ).

Анимацию теперь можно загружать лениво .

По умолчанию Angular CLI генерирует автономные компоненты, автономные директивы и автономные каналы. ng new также по умолчанию обеспечивает загрузку автономного компонента. Это поведение можно отключить с помощью ложного переключателя --standalone.

Оператор перехватчика ng g генерирует функциональные перехватчики.