Что нового появилось в Angular 6?
В этой статье я сделаю краткий обзор основных захватывающих обновлений, которые принес нам Angular 6, включая RxJS 6, Angular Elements, Ivy рендеринг и многое другое.
RxJS 6
Angular 6 теперь использует RxJS 6. В RxJS 6 несколько изменен способ импорта.
Если в RxJS 5 вы бы писали
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
const squares$: Observable = Observable.of(1, 2)
.map(n => n * n);
А в RxJS 5.5 это:
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { map } from 'rxjs/operators';
const squares$: Observable = of(1, 2).pipe(
map(n => n * n)
);
В RxJS 6.0 аналогичная запись выглядела бы подобным образом:
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
const squares$: Observable = of(1, 2).pipe(
map(n => n * n)
);
Но конкретно сейчас при мигрирации на Angular 6 можно не переписывать все импорты, а подключить библиотеку rxjs-compat
, которая позволит вашему коду использовать старый синтаксис.
Также стоит отметить что был выпущен очень классный набор правил tslint, называемый rxjs-tslint
. Он содержит 4 правила, которые при добавлении в Ваш проект автоматически переносят все импорты RxJS и код RxJS в совершенно новую версию с помощью простой консольной команды
tslint --fix
Так же подобная операция может быть выполнена глобальной установкой rxjs-tslint
с помощью команды
npm i -g rxjs-tslint
и запуском с консоли с указанием пути к файлу конфигов json:
rxjs-5-to-6-migrate -p [PATH_TO_TSCONFIG]
Для проектов на Angular CLI подобная операция будет выглядеть так:
rxjs-5-to-6-migrate -p src/tsconfig.app.json
Angular Elements
Angular Elements дают возможность обернуть ваши компоненты Angular в виде веб-копонент и встроить их в non-Angular среду ( к примеру jQuery app или VueJS app). Angular Elements существовали уже некоторые время до этого, но ранее были доступны в экспериментальном формате. С помощью v6 эта разработка продвинулась вперед и теперь входит в состав фреймворка. Возможность создавать и публиковать веб-копоненты Angular и использовать их на любой HTML странице это революционный прорыв. Представьте себе, что теперь вы сможете использовать Angular date-picker в вашем уже готовом React app. С Angular Elements у Вас появилась такая возможность!
Я думаю, мы рассмотрим эту тему более детальней в следующих статьях — напишите в комментариях желаете ли Вы узнать больше о ‘Angular Elements’.
Регистрация сервисов при помощи providedIn
Теперь существует новый способ регистрации сервисов — непосредственно внутри декоратора @Injectable()
с использованием нового атрибута providedIn
вместо ранее исспользованого providers
. Метод providedIn
принимает в качестве значения ‘root’ или любой модуль вашего приложения. Когда вы используете ‘root’, ваш сервис будет зарегистрирован как singleton в приложении, и вам не нужно добавлять его к providers корневого модуля. Аналогичным образом, если вы используете providedIn: UsersModule
, сервис регистрируется как provider модуля UsersModule без добавления его к остальным компонентам.
@Injectable({
providedIn: 'root'
})
export class UserService {
}
Этот новый способ был введен, чтобы лучше организовать дерево модулей в приложении, он включен по умолчанию и его стоит использовать в проектах на Angular 6.
Если вы пишете сервис, вы его обычно используете, но сторонние модули иногда предлагают сервисы, которые вы не используете, и вы получаете большой набор бесполезного JavaScript. По этой причине это новшество будет особенно полезно для разработчиков библиотек.
Теперь Вы можете объявить InjectionToken
и напрямую зарегистрировать его с providedIn
, а так же прописать ему factory
:
export const baseUrl = new InjectionToken('baseUrl', {
providedIn: 'root',
factory: () => 'http://localhost:8080/'
});
Обратите внимание, что это также упрощает модульное тестирование — мы можем зарегистрировать сервис в тестовом модуле, что бы получить возможность протестировать.
beforeEach(() => TestBed.configureTestingModule({
providers: [UserService]
}));
Если UserService
использует providedIn: 'root'
, то аналогичная запись будет выглядеть так:
beforeEach(() => TestBed.configureTestingModule({}));
Не все службы, зарегистрированные с помощью providedIn будут загружаться в тест, они загрузятся только если для тестирования необходимы.
ElementRef<T>
В случае если вам нужно получится ссылку на элемент в своем шаблоне, вы можете использовать @ViewChild , или @ViewChildren, или непосредственно создать ее при помощи ElementRef.
@ViewChild('loginInput') loginInput: ElementRef;
ngAfterViewInit() {
// nativeElement is now an `HTMLInputElement`
this.loginInput.nativeElement.focus();
}
Анимация
Больше нет необходимости устанавливать web-animations-js
для создания анимации в Angular 6.0, за исключением случаев когда вы используете AnimationBuilder
. Вы можете использовать API element.animate, а в случаях когда браузер не поддерживает API element.animate
, Angular 6 будет использовать CSS keyframes.
preserveWhitespaces
preserveWhitespaces: false
Эта особенность введена еще в Angular 4.4, но так как ранее флаг по умолчанию имел значение true
, то сейчас это false
. Флаг был введен для увеличения производительности — со значением false
он удаляет лишние пробелы, что может улучшить сгенерированный размер кода, а также ускорить создание компонентов.
ngModel и formControl
Ранее была возможность иметь ngModel
и formControl
в тех же полях ввода формы, но теперь это устарело, а поддержка будет удалена в Angular 7.0. Это было несколько запутанно и, вероятно, не выполняло именно то, что вы ожидали.
Поэтому подобное использование кода теперь выдаст предупреждение:
<input [(ngModel)]="user.name" [formControl]="nameCtrl">
Вы можете настроить свое приложение для отображения подобных предупреждений always
(дефолтное) / once
/ never
таким образом:
imports: [
ReactiveFormsModule.withConfig({
warnOnNgModelWithFormControl: 'never'
});
]
В любом случае для дальнейшей работы с Angular и, возможно, в дальнейшей миграции на Angular 7.0 лучше использовать или ngModel
, или formControl
для одного элемента.
Ivy: новый render в Angular
Ivy — следующее поколение Angular рендеринга. Это изменение на написание кода никак влиять не будет и заметить его можно будет по причине того, что Ваше приложение становится все быстрее и меньше.
Для тех кто не в курсе: Angular компилировал Ваши шаблоны с помощью View Engine в версиях Angular 2-4 и Ivy это будет 3-е изменение рендеринга за историю Angular.
Новая версия рендеринга не изменит стиль написания Ваших шаблонов, но добавит некоторые улучшения:
- время сборки
- размер bundle
В Angular 6 способ Ivy рендеринга не включен по дефолту, а чтобы использовать его в своем приложении нужно прописать флаг:
"angularCompilerOptions": {
"enableIvy": true
}
Рассмотрим в чем же принципиальное отличие на простом примере: PonyComponent
принимает PonyModel
(с именем и цветом) в качестве ввода и отображает изображение в зависимости от цвета, а так же выводит имя.
@Component({
selector: 'ns-pony',
template: `<div>
<ns-image [src]="getPonyImageUrl()"></ns-image>
<div></div>
</div>`
})
export class PonyComponent {
@Input() ponyModel: PonyModel;
getPonyImageUrl() {
return `images/${this.ponyModel.color}.png`;
}
}
Код, созданный с помощью старого средства визуализации
Средство рендеринга, введенное в Angular 4, создало класс для каждого шаблона, называемый ngfactory
:
export function View_PonyComponent_0() {
return viewDef(0, [
elementDef(0, 0, null, null, 4, "div"),
elementDef(1, 0, null, null, 1, "ns-image", View_ImageComponent_0),
directiveDef(2, 49152, null, 0, i2.ImageComponent, { src: [0, "src"] }),
elementDef(3, 0, null, null, 1, "div"),
elementDef(4, null, ["", ""])
], function (check, view) {
var component = view.component;
var currVal_0 = component.getPonyImageUrl();
check(view, 2, 0, currVal_0);
}, function (check, view) {
var component = view.component;
var currVal_1 = component.ponyModel.name;
check(view, 4, 0, currVal_1);
});
}
Основными частями этого кода являются:
- структуру DOM состоящая из созданных элементов (div, img), их атрибуты и определения. Каждая часть DOM структуры в массиве представлена индексом.
- функцию обнаружения изменений, в которой записан код, используемый для проверки того, соответствуют ли выражение, используемые в шаблоне, тем же значениями, что и раньше. Здесь он проверяет результат метода
getPonyImageUrl
и, если он изменяется, обновляет картинку, а так же проверяет имя и, если нужно, обновляет текстовое поле.
Код, созданный с помощью Ivy
Если для приложения на Angular 6 установить флаг enableIvy
в значение true
, то для подобного примера не сгенерируется ngfactory
, но информация поступит непосредственно в статическое поле самого компонента (упрощенный код):
export class PonyComponent {
static ngComponentDef = defineComponent({
type: PonyComponent,
selector: [['ns-pony']],
factory: () => new PonyComponent(),
template: (renderFlag, component) {
if (renderFlag & RenderFlags.Create) {
elementStart(0, 'figure');
elementStart(1, 'ns-image');
elementEnd();
elementStart(2, 'div');
text(3);
elementEnd();
elementEnd();
}
if (renderFlag & RenderFlags.Update) {
property(1, 'src', component.getPonyImageUrl());
text(3, interpolate('', component.ponyModel.name, ''));
}
},
inputs: { ponyModel: 'ponyModel' },
directives: () => [ImageComponent];
});
// ... rest of the class
}
Все это теперь содержится в этом статическом поле. Атрибут template
содержит эквивалент ngfactory
, который мы использовали, но с немного другой структурой. Функция шаблона будет запускаться при каждом изменении, как раньше, но имеет 2 режима:
- Статический режим выполняется когда компонент только создается и содержит статические DOM ноды
- Динамический режим выполняется при каждом изменении (при необходимости обновить изображение или текстовое значение)
Несколько приятных плюшек и подведение итогов
Теперь все декораторы встроены непосредственно в их классы (это то же самое для @Injectable, @Pipe, @Directive) — это означает, что когда компилятор переводит ваш шаблон в JavaScript, разрешено использовать информацию, непосредственно доступную для компонента и его декоратора (он не может смотреть на dependencies). Файл metadata.json больше не требуется, что упрощает тулзам обработку вашего кода, и больше инструментов становятся совместимыми с пакетами npm. Это упрощает как создание, так и потребление пакетов! И, конечно же теперь у вашего приложения будет более быстрое время перекомпиляции — изменение одного компонента с меньшей вероятностью вызовет перекомпиляцию для всего приложения.
Теперь с легкостью можно обновить автоматически Ваши @angular
зависимости в файле package.json
ng update
Если Вы создаете свои библиотеки на благо человечеству, вы можете создать схему, которая будет использовать ng update
для того, что бы ваша библиотека обновлялась у пользователей. ng add
— это способ легко и просто добавлять новые возможности в ваше приложение.
ng add @angular/pwa
ng add @angular/elements
ng add @angular/material
ng add @nativescript/schematics
Сгенерированный код немного меньше, но, что более важно, упрощена структура взаимозависимостей, что позволяет быстрее перекомпилировать, когда вы меняете одну часть приложения.