NG Stream
248 subscribers
85 photos
4 videos
2 files
465 links
Авторский канал об Angular и новостях фронтенда. Мы рассказываем о том, с чем сталкиваемся в процессе разработки, делимся полезными, на наш взгляд, статьями.

Made by devs for 🅰️ devs

Авторы:
- @antoonG
- @basters

Чат: https://t.me/+JYGeE7HekO04Y2My
Download Telegram
В дополнение к посту об уже добавленных async redirects. Команда планирует добавить пару довольно полезных фичей в Angular Router 🎉.

Первое это добавление поддержки canDeactivateChild, о котором просили лет 9 назад. MR с ссылкой на issue.

И второе добавление injection context для loadComponent/loadChildren callback, что является решением архитектурных ограничений. На практике означает, что мы сможем инжектить сервисы напрямую в функцию загрузки, динамически подгружать необходимые компоненты, опираясь, например, на состояние в сервисе, на работу с фича флагами/на основе ролевой модели приложения. В общем, возможность реализовать кастомную логику загрузки и не писать велосипеды. Да, подчеркну это и здесь!
На данный момент некоторые разработчики используют workaround по обращению к injection context в loadChildren, который был предложен Игорем Кацубой. И ссылка на feature request, с которым можно ознакомиться более детально.

Существенное улучшение DX на подходе!🎉

#angular

NgStream / Чат
🔥6
Небольшая новость. Со следующей версии Angular в тестах можно будет использовать input bindings. Пару недель назад влили МР, добавляющий данную функциональность для TestBed.

Теперь при вызове TestBed.createComponent можно указать input, который хотели протестировать. Это избавляет от необходимости фигачить wrappers для каждого компонента. Кроме того, сохраняется консистентность между тестами.

Честно говоря, я думал, что такое уже есть. Дело в том, что уже длительное время пользуюсь spectator, где этот вопрос решается через spectator.setInput().

it('should...', () => {
spectator.setInput('className', 'link');
...
});


Метод изменяет значение @Input() и вызывает хук ngOnChanges().
Очень удобно. Есть и минусы с тем, что получаем разные подходы при написании, одни делают через TestBed, другие с Spectator. Вызовы перемешиваются и выглядит не очень. Еще и не всегда могут корректно взаимодействовать друг с другом. Но это другая история.

А какие инструменты для написания тестов используете вы? Если пишете, конечно;) Как решаете вопрос с тестированием input, output?

#angular

NgStream / Чат
👍4🔥3
Премия Тьюринга

В этом году Andrew Barto и Richard Sutton получили премию за вклад в развитие обучения с подкреплением(RL). Эти ученые заложили основы технологии, используемой в Google DeepMind, системах для обнаружения мошенничества, разработке алгоритмов для автономных машин. Их исследования позволили создать модели, которые обучаются на основе проб и ошибок.

Премия Тьюринга - это аналог Нобелевской премии в нашей индустрии. Награду вручает АСМ за фундаментальные открытия в Computer Science. Спонсирует Google. Считаю важным рассказать о некоторых открытиях и именах, изменивших индустрию!

1966 Алан Перлис - награжден за подходы к разработке компиляторов. Благодаря ему мы делаем платформонезависимые языки, описывая их формальными грамматиками. "Языки должны быть понятны и людям, и ЭВМ, хороший язык - не только инструмент, но и средство общения."

1968, Ричард Хэмминг - за способ кодирования, который позволяет исправлять ошибки при передаче данных. Сеть, hdd, ram используют коды Хэмминга по сей день.

1972, Эдсгер Дейкстра - за фундаментальный вклад в разработку структурного программирования. Критика использования оператора GOTO, популяризация концепции "слабого связывания". Заложил основы написания чистого и поддерживаемого кода.

1973, Чарльз Бахман - за разработку концепции сетевых баз данных и создание Integrated Data Store (IDS), первой промышленной СУБД в истории. Его мысли приведут нас к sql и orm, но позже.

1974, Дональд Кнут. Его труд "Искусство программирования" знают многие. Изощряй ум алгоритмами, но помни: "Преждевременная оптимизация ‐ корень всех зол".

1976, Майкл Рабин и Дана Скотт. Введение концепции недетерминированных конечных автоматов (NFA) и доказательство его эквивалентности детерминированному. Основа для современных регулярных выражений, алгоритмов поиска и верификации ПО.

1977, Джон Бэкус. Введение BNF-нотации для описания синтаксиса языков. Автор языка Фортран – первый массовый ЯП высокого уровня, используется до сих пор, а очередная версия вышла в 2023.
Данные работы заложили фундамент всей современной computer science — от компиляторов до искусственного интеллекта. Без этих открытий не существовало бы Google, блокчейна, OpenAI или даже обычных мобильных приложений.

Это первый из серии постов об исторических личностях, повлиявших на нашу индустрию.

#людиЦелиДостижения

NgStream / Чат
3🔥1
Обновления инструментов разработчика

🛠 В 20 версии было заявлено об улучшении DevTools и внутреннем партнерстве с Chrome. И, как результат, добавили Angular - Custom track в разделе Performance. С ним проще отслеживать рендеринг компонентов, CD, обработку событий. Раньше приходилось переключаться между DevTools и браузерными инструментами, что усложняло анализ.
Для включения надо обновиться и вызвать в консоли ng.enableProfiling();

В Angular - Custom track (performance) показываются данные, которых нет в Angular DevTools (Это немного разное, не путаться!). Например, создание провайдеров. В доку в раздел best-practices также добавили раздел c пояснениями.

🛠 А в 20.1 дополнят DevTools инструментом Signal graph. Он будет доступен в experimental режиме. Для работы с ним переходим в Angular DevTools, Settings, выбираем компонент в дереве и справа "Show Signal Graph".

Какие улучшения сможем увидеть? В первую очередь, взаимосвязь между сигналами, разновидности используемых в компоненте сигналов (signal(), computed(), linkedSignal(), effect()). По клику на каждый можно перейти в код, просмотреть имя, значение, тип и epoch - сколько раз было изменено значение.

Для отслеживания изменений неплохо, но хотелось бы вручную изменять значения в самом инструменте. Фичу, конечно, планируют улучшать. В любом случае хорошо, что есть движение в сторону развития DevTools.

#angular

NgStream / Чат
👍9
Signal-based формы - заметки команды

Те, кто уже вовсю использует сигналы в приложениях, задались вопросом, как удобно, просто и без костылей взаимодействовать с формами. Я на неделе решил посмотреть, что команда angular готовит с сигнальными формами. Для желающих ознакомиться подробнее - прикрепляю ссылку. Отмечу, что все это еще очень ранний прототип и многое могут изменить. Поэтому кратко хочу отметить основные отличия.

📌 Как я понял, ключевой принцип в том, что форма не будет хранить данные внутри - они будут храниться в модели, а форма, по сути, обертка для удобной работы с хранением статуса, валидацией и тд. Единый источник правды - модель. Изменения идут через форму, а она меняет модель.

📌 Форма это дерево из объектов Field, которые хранят состояние. Для получения состояния обращаемся к инстансу Field. Он возвращает FieldState, который хранит value, errors, disabled и т.д. Каждый из них это сигнал, который не устанавливается напрямую, а возвращает значение, исходя из логики, которая описана через Schema.

// Create a model containing the initial form data.
userModel = signal<User>({name: '', username: ''});

// Create the form, linking it to the model.
userForm = form(this.userModel);


📌 Следующее отличие - отсутствие FormArray. Т.к. форма это, грубо говоря, ссылка на модель данных, взаимодействие будет с обычными массивами модели.

model = signal<Order>({
...
items: [
{ description: 'item', quantity: 1 },
]
});
orderForm: Field<Order> = form(this.model);
itemsField = this.orderForm.items;


📌 Декларативное определение логики полей через Schema. Это набор правил - функций: валидаторов, условий для установки disabled, required, error и тд. Schema выполняется единожды при создании формы и предназначена для определения структуры логики полей и вычислений.

const userSchema = schema((path) => {
disabled(path.username, () => true, 'Username cannot be changed');
required(path.name);
error(path.age, ({value}) => value() < 18, 'Must be 18 or older');
});
userForm = form(this.user, userSchema);


📌 valueChanges заменен на computed(), effect() и прочие signal API.

📌 Для кросс-валидации предполагается обращаться к другим полям либо в родительской схеме, либо использовать valueOf(). Также можно делать композицию правил валидации, объединяя логику из нескольких схем, используя apply() или applyEach() для работы с элементами массива. А applyWhen() / applyWhenValue() для добавления логики вокруг контролов. Подразумевается, что это более удобный и декларативный вариант, заменяющий if (…) { addControl() }.

📌 Заменой асинхронным валидаторам являются функции validateHttp()/validateAsync(), вызываемые в "схеме". Во время валидации срабатывает встроенный pending state.

validateHttp(name, {
request: () => `/api/check?${name}`,
errors: (data) => {
if (data === 'OK') return [];
return [{kind: 'error', message: data}];
}
});


📌 Синхронизация состояния. Для связи модели и шаблона используется директива [control]. Она обеспечивает "two-way binding" между состоянием элемента формы (value, disabled, touched, aria-attributes) и UI.

Вывод: интересные мысли и немногословный, декларативный, типобезопасный подход с использованием всех преимуществ сигналов. Но пока ничего не сказано о миграции и почти ничего об интерфейсе ControlValueAccesssor.

#angular #signal_forms

NgStream / Чат
🔥14
В следующей версии (20.2) Zoneless стратегия перейдет из developer preview в статус stable! Соответствующий МР был опубликован. Это означает, что Zone.js больше не будет участвовать в механизме обнаружения изменений. С использованием сигналов получится добиться наилучшей производительности. По крайней мере, так заявляют разработчики Angular team.

📌 Несмотря на всю свою мощь, Zone.js - это архитектурный компромисс, который дает удобство в обмен на свободу и производительность. Из недостатков:

- расходы на постоянный перехват асинхронных операций. Проверки происходят чаще, чем необходимо.
- увеличенный размер бандла. И, как следствие, большее время запуска приложения.
- совместимость с экосистемой. Некоторые функции, например, async/await не могут быть эффективно пропатчены и требуют дополнительных преобразований. Неявная связь между любым setTimeout и CD.

📌 Изменение принципа работы

В новом подходе ввели понятие Local Change Detection. Если при OnPush стратегии при изменениях в компоненте флагом помечается часть дерева view от текущего до верхнего, а изменения идут от root сверху вниз, то с использованием Signal API используются новые флаги: RefreshView и HasChildViewsToRefresh‎. Компонент, в котором изменился сигнал, помечается RefreshView, а все его родительские флагом HasChildViewsToRefresh‎. В этом случае будет проверен и обновлен только компонент с RefreshView, а все остальные будут пропущены.

При генерации новых приложений через schematic по умолчанию будет исключена зависимость Zone. Но не забываем об enterprise решениях, которые могут быть завязаны на сторонние либы с zone внутри. Получается, обновление будет не для всех… Или нет?

📌 Миграция

1. Первоначальная задача, в этом случае, сделать аудит зависимостей, выделив части, которые полагаются в расчетах на Zone и поэтапно переписать. Для них решением будет создание оберток. Обертка - компонент, с input() для передачи данных. Внутри обертки прослушиваем события библиотеки и вручную отслеживаем изменения.
2. Подготовка команды. Разработчики должны уметь работать с новой парадигмой. Любителям использовать Promise и addEventListener, вместо реактивного подхода, придется отказаться и все же перейти на Signal API и RxJS.

Миграция требует экспертизы, но окупается за счёт снижения затрат на поддержку и погашение технического долга. И успешный переход возможен только при наличии заинтересованной команды и стратегического взгляда на развитие проекта.

Гайд по Zoneless

#angular

NgStream / Чат
🔥81❤‍🔥1🎉1🍓1
В прошлом месяце Evan You анонсировал выход rolldown-vite. Это пакет, который призван в будущем заменить текущий сборщик vite(на основе Rollup и esbuild).

В чем суть?

Rolldown — новый Rust-сборщик для Vite, входит в Oxc (JavaScript Oxidation Compiler, набор инструментов для JS/TS, включающий парсер, линтер и минификатор). Обещают значительное ускорение сборки в 3–16 раз и снижение потребления памяти до 100 раз. Что-то довольно оптимистичные заявления😅

📌 Т.к. все преобразования и минификация теперь выполняются через Oxc, это устраняет зависимость от esbuild (если только её не требуют плагины).

📌 Я заметил, что переход довольно простой - "drop-in replacement", достаточно заменить пакет vite на rolldown-vite в package.json и все. Сейчас его можно попробовать на своих пет-проектах.

📌 Из минусов пока отметил бы:
- Совместимость: еще не все плагины протестированы и могут потребовать доработок.
- Стабильность: форк пока еще в бете, для использования в нормальном проекте стоит подождать Phase 2, когда rolldown-vite перейдет в статус stable и будет объединен с основной кодовой базой).
- Кривая обучения: если кастомизировали сборку Vite, возможно, придётся адаптировать конфиги.

Возможно, где-то ошибаюсь, т.к. ковырять плагины не приходилось. Если есть что сказать, welcome в комментарии)

Что касается Angular, то CLI с 16 версии перешел на esbuild-based систему сборки. Мы получили поддержку Vite - для dev режима, esbuild для dev и prod сборки. Как мне кажется, есть вероятность, что в будущем мы можем получить еще более улучшенную по времени и по памяти сборку, если все-таки rolldown-vite заменит основной сборщик.

#js #angular

NgStream / Чат
👍3
И дополнение темы сборки. Команда Angular влила МР, заявив о переходе на Rolldown вместо Rollup, что позволит оптимизировать работу сборщика и будет плюсом при загрузке приложения, т.к. потребуется загружать меньше chunks.

При этом фича пока что в статусе experimental и находится под флагом, соответственно, не будет использоваться по умолчанию для всех проектов.

#angular

NgStream / Чат
👍5🔥1
Deep signals

Еще одно нововведение сигналов планируется к внедрению командой Angular! Посмотрим недавно опубликованный draft.

deepSignal - это WritableSignal, который отслеживает значение только указанного свойства из родительского сигнала. Если перезаписать значение, то оно обновится в родительском сигнале. Сочетает вывод значения и обновление значения в родителе, что отлично подходит для работы с иерархической структурой.

const model = signal({user: {name: 'Alex'}, company: 'Google'});
const user = deepSignal(model, 'user');

console.log(user()); // {name: 'Alex'}

// Updating `user` updates the parent.
user.set({name: 'Bob'});
console.log(model()); // {user: {name: 'Bob'}, company: 'Google'}


Сравним с другими сигналами:

📌 computed
- Автоматически вычисляет значение на основе родительских сигналов.
- Запрещены ручные изменения.

📌 linkedSignal
- Значение берется от родителя ИЛИ задается вручную.
- Меняет только локальное значение, не изменяя родительский сигнал.

📌 deepSignal
- Чтение и запись родительского сигнала. Но только указанного свойства!

Производительность
Если представить структуру графа, то изменение сигнала на верхнем уровне приведёт к изменению всех зависимых сигналов. deepSignal решают эту проблему, используя информацию о том, какое именно свойство изменяется в родительском значении. Кроме того, на основе deepSignal появилась возможность конструировать еще один тип сигналов.

structuralSignal - это «ленивая» версия computed, которая не реагирует на точечные изменения (свойств) в deepSignal. А реагирует только на полную замену значения родителя.

const addressForm = deepSignal({ street: 'Цветочная', city: 'SPB' });
const formChanges = structuralSignal(addressForm);

addressForm.set(v => { v.street = 'Московский' });
addressForm.set({ street: 'Садовая', city: 'RND' });


Оба сигнала еще в статусе draft и реализация, соответственно, может измениться. Предположу, что это будет дополнением к прототипу, в котором команда Angular описывала будущее сигнальных форм. Я делал заметки по документу здесь.

Как мы видим, набор инструментов, на котором могут быть сделаны сигнальные формы, дополняется. Для желающих ознакомиться подробнее - прикреплена ссылка выше.

#angular

NgStream / Чат
👍5🤪3🤔1
Давайте поговорим еще об одном инструменте из TypeScript: T[number].

Это вывод типов (derived types) с использованием indexed access types. Данный подход может пригодиться для объявления типа вместо enum. О минусах работы с ним я писал здесь.

Для примера определим массив и через typeof получим тип.

const possibleResponses = [
{
status: 200,
body: 'Success',
},
{
status: 404,
body: 'Not found',
},
];
type Responses = typeof possibleResponses;
// type Responses = { status: number, body: string }[];


Сейчас тип это массив объектов с полями status,body. Попробуем убрать массив из типа и получить тип самого объекта. Применим решение с T[number].

type Responses = possibleResponses[number];


В этом случае мы говорим вывести все свойства объекта, доступ к которым осуществляется через numeric key.

Важно задекларировать исходный массив as const, что заставит вывести буквальные значения массива. Иначе TS извлечет тип string[].

Пример использования:
const roles = ['admin', 'editor'] as const;
type RolesType = typeof roles;
type Role = RolesType[number];
// type Role = 'admin' | 'editor';


Прикрепляю ссылку с примерами использования deriving types.

#typescript

NgStream / Чат
5🤔1
Новости предстоящего обновления

📌 В версии 20.2 появится возможность указывать алиас as в блоке else if, что немного упростит чтение кода. Соответствующий МР появился на прошлой неделе.

@if (template) {
template 1
} @else if (loaderTemplate(); as state) {
<skeleton [loading]="state.loading">
}


Вы можете заметить некоторые пересечения с использованием в шаблоне директивы @let, добавленной пару версий назад. Если посмотреть на CanIUse, то статус stable с 18.2. Но сходство заметно только на первый взгляд.

В чем же разница?

- @let позволяет задать полученное значение на более широкий контекст. Например, на весь шаблон.
- as все-таки предназначен для случаев, когда значение требуется только в одном блоке. В общем, у него меньше зона покрытия. Эту идею стоит учитывать при выборе инструмента.

📌 Свойство lastSuccessfulNavigation из Router API станет сигналом.

// 20.1
const navigation = this.router.lastSuccessfulNavigation;
// 20.2
const navigation = this.router.lastSuccessfulNavigation();

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

Ожидаем обновлений с переходом остальных элементов Router на сигналы!

📌 Разработчики улучшили вывод типов при работе с событиями DOM. Т.к. тип EventTarget | null слишком общий, он доставляет некоторые неудобства при обработке и получении value c input. При этом не всегда возможно сделать cast типов (as HTMLInputElement). Например, в шаблоне.
Теперь для void элементов, например, <input>, <img>, <br>, где всплытие невозможно, тип $event.target будет определяться автоматически. Это уменьшит количество ошибок TypeScript и не будет необходимости писать лишние проверки.

Ссылка на МР.

#angular

NgStream / Чат
🔥91
Новости signal-based forms

Команда Angular обновила прототип сигнальных форм, перетащила в отдельный пакет @angular/forms/signals и планирует влить в master.

Насколько я понял, на данном этапе пакет не будет доступен (нет package.json) и он с пометкой, что дизайн API еще в разработке и может измениться. МР с кодом, большим количеством тестов и документацией можно посмотреть по ссылке.

Как и в заметках, показанных ранее для ознакомления, подчеркиваются основные идеи:

📌 Формы состоят из 4 основных блоков: Data Model, Field State, Field Logic, UI Controls.
📌 Ключевой принцип в том, что форма не хранит данные внутри. По сути, это ссылка на модель. Единый источник правды хранится в WritableSignal. При обновлении модели обновляется сигнал в форме и, наоборот, при обновлении формы обновится модель.

userModel = signal<User>({name: '', username: ''});
userForm = form(this.userModel);


📌 Как и с другими сигналами, form() требуется объявлять в injection context или передав ссылку на контекст.

private readonly injector = inject(Injector);
...
const form = form(this.userModel, {injector: this.injector});


Заметки команды, показанные ранее, я разбирал в канале. Тогда в них не было ничего сказано об интерфейсе ControlValueAccessor.

🛠 Сейчас в коде я нашел класс, обеспечивающий interop. Отмечено, что это заглушка NgControl для обеспечения совместимости с текущей версией контролов. Он реализует только часто используемые свойства и методы настоящего NgControl. Этот класс используется, например, в директиве [control].

🛠 Директива [control] связывает поле формы (FieldState) и шаблон (UI), обеспечивая двустороннюю привязку данных и состояние формы. Из описания видно, что она может работать с CVA, но только в режиме совместимости для реактивных форм и стоит предпочесть сигнальный FormValueControl, нативный html элемент. Получается, что для сигнальных форм необходимости в CVA не будет, а для разработки собственных контролов предполагается реализовывать интерфейс FormValueControl. А NgControl, скорее всего, будет помечен, как deprecated через какое-то время после релиза.

В общем, видно, что переход на сигналы еще активно разрабатывается и полная картина, включая миграцию проекта с Reactive/Template форм, пока не совсем ясна. Ждем обновлений!

#angular #signal_forms

NgStream / Чат
🔥82❤‍🔥1👍1
В ESLint 9.34.0 будет доступен многопоточный линтинг

📌 По заявлениям команды ожидается прирост примерно в 1.5 - 3 раза для крупных проектов. Добиться ускорения получилось за счёт использования механизма worker threads из Node.js API.

📌 Ранее, задачи, которые больше всего нагружали CPU - парсинг AST дерева, работа сложных правил, выполнялись в одном потоке Event Loop, что могло приводить к его блокировке. Теперь main thread становится менеджером - он создает пул worker threads, управляет задачами, перенаправляя их в другие потоки и агрегирует данные.

📌 Основная проблема была в том, что worker threads обмениваются данными с главным потоком через механизм structured clone, что не позволяло передать довольно сложную конфигурацию, включающую кастомные правила, инстансы плагинов и тд. Решить этот вопрос позволяют Options Modules. Вместо передачи конфига каждый worker клонирует его и работает с независимой копией.

📌 Также было ограничение с обработкой только одного файла, теперь появилась возможность параллельного выполнения.

📌 На примере показано объявление ECMAScript модуля и создание инстанса через ESLint.fromOptionsModule(), передав в нее ссылку на конфиг.

const configURL = new URL("./my-eslint.config.js", import.meta.url).href;
const optionsModuleText = `
import config from ${JSON.stringify(configURL)};

export default {
concurrency: "auto",
overrideConfig: config,
overrideConfigFile: true,
stats: true,
};
const optionsURL = new URL(`data:text/javascript,${encodeURIComponent(optionsModuleText)}`);
const eslint = await ESLint.fromOptionsModule(optionsURL);


📌 Команда предупреждает, что еще возможны баги с некоторыми конфигами/плагинами, их будут фиксить. Кроме того, пока остается проблема для случаев, когда правил слишком много или у них долгая инициализация. В своей заметке еще делятся советами по настройке и использованию, ознакомиться можно по ссылке.

NgStream / Чат
🔥12🤔2
Navigation API

📌 На прошлой неделе в репозитории Angular появился МР, в котором говорится о поддержке экспериментального Navigation API! Это показывает желание более тесно интегрироваться с платформой и использовать будущий стандарт индустрии вместо самописных решений.

📌 Спецификация самого API еще находится на этапе обсуждения в WICG (Web Platform Incubator Community Group). Хотя, в целом, ее одобряют, детали реализации (сигнатуры методов, названия событий) теоретически могут измениться. Кроме того, потребуется время на миграцию, это не просто замена одного метода на другой, а смена парадигмы управления навигацией.

📌 Судя по комментарию к МР, новый API пока что может ломать некоторые тесты, но есть временное решение с TestBed.

Что такое Navigation API?

🛠
Это новый стандарт, предоставляющий интерфейс управления историей переходов и URL, заменяющий устаревший History API. Задача - сделать навигацию более предсказуемой, надежной и удобной для разработчиков.

🛠 Поскольку History API вообще не предназначен и не проектировался для управления целым приложением, это приводит к усложнению кода при попытке написать какую-либо нетривиальную логику, трудностям с обработкой кнопок браузера "Назад/Вперед" и тд.

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

🛠 Какие ограничения накладывает данное API?

- Компромиссы в текущей реализации. Например, navigate() не срабатывает при первоначальной загрузке страницы. Это подходит для SSR, но требует доработки логики для SPA (MPA), т.к. нужно инициализировать код, отвечающий за рендеринг начального состояния страницы.
- Работает только в контексте одного окна или одного iframe, но не между ними, т.е. он работает только с историей текущего топ-левел окна или конкретного iframe.
- Не позволяет модифицировать последовательность записей в навигации. Сценарий с закрытием модалки и удалением временного url, который использовался для открытия из истории будет невозможен с новым подходом.

Что с поддержкой у браузеров?

Здесь пишут, что на данном этапе проблемы с поддержкой только у Firefox. Даже Safari имеет ограниченную поддержку.

Новый подход сможет улучшить DX и стандартизировать существующие решения. Ждем обновлений!

#js #angular

NgStream / Чат
👍5🤔1
Возможно, вы слышали, что разработчики Angular добавили целый раздел по работе с AI. Пару недель назад команда представила инструмент по миграции приложения на zoneless с помощью AI. Это MCP сервер, который автоматизирует процесс миграции.

Стоит коротко сказать, что такое MCP.

📌 MCP - Model Context Protocol, открытый протокол взаимодействия LLM моделей и всевозможных инструментов.

📌 Эта штука предоставляет AI прямой доступ к самой актуальной информации: официальной доке, текущим рекомендациям и структуре конкретного проекта, например, монорепе или набору библиотек.

📌 Для этого у MCP есть набор инструментов, которые используются при ответе на вопрос:

get_best_practices
Получает текущие стандарты написания кода для Angular.

search_documentation
Ищет ответ в доке, что понятно по названию.

list_projects
Анализирует структуру workspace.

📌 Данный подход решает ключевую проблему LLM - работу на устаревших данных. Также гарантирует, что будут предлагаться более корректные и актуальные решения. Аналогично Language Server Protocol организует взаимодействие между редактором и языковым сервером, но, в данном случае, взаимодействие происходит между LLM, CLI и проектом, анализируя и получая данные о миграции. В файлах даже есть описание требований, которые предъявляются к каждому этапу миграции.

Насколько я знаю, MCP есть уже в Figma, где можно указывать ссылку на макет, либо его часть, и модель будет генерировать ответ. Правда не всегда корректно работает, подвисает, требует перезагрузки и макеты должны быть хорошо сделаны (указаны шрифты, глобальные цвета).

Уже заметна тенденция - более точная работа AI с разворачиванием специализированного MCP-сервера с более точным и актуальным контекстом. Возникает вопрос, насколько большие проекты можно будет отдавать модели и как быстро будет идти миграция.

📌 Если вам интересна эта тема, то на 16 сентября анонсировали Angular AI Event, на котором расскажут о работе AI, инструментах и возможных интеграциях! Ссылка на live трансляцию на youtube.

И в пятницу опубликовали сообщение в блоге с списком недавних обновлений 20.1 и 20.2. Summer update ;)

#angular

NgStream / Чат
👍51🤪1
Со следующей версии использование функции provideHttpClient будет необязательным. Добавлять в список providers[] потребуется только для указания дополнительных параметров, например, withFetch() или withInterceptors([]).

Что это значит?

Что зависимость автоматически будет providedIn: root на уровне приложения. И далее можно будет сразу в нужном классе инжектить зависимость HttpClient.

@Injectable({ providedIn: 'root' })
export class HttpClient {}


#angular #angular21

NgStream / Чат
❤‍🔥64🔥3🤔1
Jetbrains анонсировали JavaScript Day на 2 октября. Митап бесплатный и пройдет онлайн. Состав спикеров впечатляющий!

Ryan Carniato, maintainer SolidJS с рассказом о сигналах, реактивности. Один из докладов, который хотелось бы послушать.

Lydia Hallie, developer advocate, Bun с темой, интересной для многих: Bun: The Fast JavaScript Runtime.

Среди других популярных спикеров Kent C. Dodds, Victor Savkin, Alexander Lichter.

#js

NgStream / Чат
🔥4
Angular выпустили предрелизную версию 21.0.0-next.2. Среди прочих нововведений есть сигнальные формы, с которыми я решил поэкспериментировать. Для желающих добавляю ссылку на stackblitz.

📌 Сразу бросается в глаза переход на декларативную валидацию и rule-based подход.

📌 Сейчас мы добавляем валидаторы в массив, навешивая на контролы или группу контролов. С signal forms процесс построен иначе.

Как и было указано ранее в заметках команды, в новом подходе, в первую очередь, нужно смоделировать модель в отдельном сигнале (источнике) и тип формы. Затем передать источник в форму и добавить валидаторы. Для связи модели и шаблона используется директива [control]. Она обеспечивает "two-way binding" между состоянием элемента формы (value, disabled, touched, aria-attributes) и UI. Каждый элемент формы это Field, которые хранит состояние. Для получения состояния обращаемся к инстансу, который возвращает FieldState, из него можно получить value, errors, disabled и т.д.

Из плюсов я бы отметил:

📌 Декларативная запись. Проще читать, выделять уровни абстракций и использовать термины из предметной области.
📌 Расширяемость. За счет заявленной schema composition. С помощью функции apply можно выносить общие правила валидации и бизнес-логики для часто используемых структур данных (например, адрес или дата) в отдельные, переиспользуемые схемы, что позволит избежать дублирования кода.
📌 Тестируемость. Поскольку такая запись зачастую является чистой функцией, ее проще тестировать, делая это изолированно.

Есть и недостатки:

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

📌 Нужно стараться писать абстрактные и гранулярные валидаторы (правила). Мнения, касательно этого пункта могут отличаться и не каждому проекту такой способ подойдёт.

Нашел и некоторые баги (или я не понял идею?).

При добавлении валидаторов required() и email() на поле email и указании в модели email: null - первый валидатор не отрабатывает вообще, массив errors() оказался пустым, а второй требует только string для входного значения. Это не совсем удобно, т.к. для проверки наличия значения необходима декларация вида None|Some<A>. В общем, даже эти два валидатора вместе дружат только с ограничениями, либо, повторюсь, есть дополнительные параметры для настройки, которые я не увидел. В итоге написал простой собственный валидатор, поскольку встроенный не отличается строгостью, так же, как и в реактивных формах.

Видно, что проделана большая работа по упрощению подхода, в целом. Ждем обновлений!

Это уже третья заметка на канале из серии о сигнальных формах!🎉

#angular #angular21 #signal_forms

NgStream / Чат
👍5🤔3❤‍🔥11
📌 Появился ресурс, на котором можно удобно посмотреть совместимость версий библиотек. Может быть полезно при обновлениях. Автор: Gerome grignon


📌 Jean Meche сделал интерактивный пример работы директивы track для элемента for. Наглядно и понятно для каких случаев какой вариант выбирать. Можно скинуть коллеге и использовать на ревью.

#js #angular

NgStream / Чат
🔥4👍1
Я наконец-то добрался до возможности написать заметки в канал. Месяц выдался напряженным.

Тем временем уже опубликованы предрелизные и RC сборки 21.0.0. Одной из самых ожидаемых фичей является Signal forms. В канале уже есть заметки, собранные по dev notes команды Angular. А вы пробовали превью сигнальных форм? Понятно, что реализация пока не рекомендуется для enterprise проектов. Я немного поковырял на пет проекте и заметил, что DX заметно подрос!

Рассмотрим стандартный сценарий - форма заказа товара, соответствующая OrderForm.

interface OrderForm {
information: FormGroup<InformationGroupControls>,
cost: FormGroup<CostGroupControls>,
additionalDetail?: FormGroup<AdditionalDetailControls>
}


📌 Reactive Forms состоят из нескольких sub FormGroups. И некоторые из них добавляются по специфичной бизнес логике. Для жонглирования синхронизации UI и модели данных приходится использовать addControl и removeControl. На прктике все это обрастает значительным количеством логики, которая потом сложна для восприятия.

📌 Signal forms. Если еще не разобрались, задам контекст. Эти формы строятся на основе готового объекта (модели данных). В нашем случае представим объект, соответствующий интерфейсу OrderForm. И здесь для управления отображением формы предусмотрен немного другой API. Есть 2 подхода. В зависимости от того, надо ли отображать модель на UI.

1 случай. Когда пользователь может захотеть добавить в форму заказа дополнительные данные, например, через чекбокс. В этом случае доп поля должны отобразиться на странице. В этом случае пользуемся свойством hidden(). При этом:

- поля присутствуют в модели, но скрыты
- сохраняется и отрабатывает валидация
- состояние сохраняется при скрытии/показе

2 случай. Данные не нужно отображать. В этом случае поля опциональны в модели, а валидация стоит применять через applyWhenValue().

orderForm = form(this.orderModel, order => {
// 1 случай. Добавлена валидация для additionalDetail:
apply(order.additionalDetail, orderDetailSchema);

// ИЛИ
// 2 случай. Добавить валидацию, если представлены данные
applyWhenValue(
order.additionalDetail,
(detail) => detail !== undefined,
orderDetailSchema,
);

// Пользователь не должен видеть часть формы, пока не отмечен checkbox
hidden(order.additionalDetail, ({valueOf}) => valueOf(order.showAdditionalDetail));
});


Еще на Q&A сессии с разработчиками говорилось, что перед созданием формы возможно объединить несколько сигналов. Например, использовать linkedSignal().

selectedItem = signal("Phone");
quantity = linkedSignal({
source: this.selectedItem,
computation: () => 1,
});


Если состояние формы инициализируется из resource(), то linkedSignal() может указать, как должно обновляться состояние формы, когда сервер присылает новое значение. Поскольку он получает старое и новое значение от сервера и знает текущее состояние, может выполнить "3-way merge". Пока что звучит непонятно и нужно смотреть детали.

С помощью встроенных правил формы получились более гибкими, но потребуется время на более детальное погружение и смену парадигмы. Предлагаемые подходы удобны за счет декларативности. Из плюсов можно отметить:

📌 Производительность без необходимости ручной оптимизации. Нет лишних проверок изменений.
📌 Совместимость с сигналами и предсказуемое управление состоянием. Самый очевидный плюс, теперь формы вписываются картину всего приложения, поскольку при первом внедрении сигналов переключения доставляли неудобства.
📌 Композиция и переиспользование. За счет schema для построения и валидации (о чем писал раньше), linkedSignal, композиция сложных форм становится более интуитивной.
📌 Типобезопасность. Типизация форм стала лучше, меньше потребности в приведении типов, как это было, например, с FormRecord<T> для динамических форм.

#angular #angular21 #signal_forms

NgStream / Чат
🔥7
На улице осень, близится релиз Angular, который мы сможем заценить в течение пары недель.

С 21 версии Vitest будет использоваться как test runner по умолчанию для новых проектов. Но пока что под флагом experimental.

Дело в том, что в 2023 году Karma стала deprecated и вставал вопрос, на какой инструмент переходить. Кстати, в течение года команда Karma все еще правила баги, критические уязвимости и тд. В итоге был выбран Vitest, который обладает мощным API, по сравнению с Jasmine и даже Jest. Кроме того, включает browser mode, что означает запуск тестов в реальном окружении. Vitest работает на базе Vite.

📌 Преимущества Vitest
- Ускорение благодаря ES-модулям;
- Нативная поддержка TypeScript;
- Встроенная поддержка моков и spies;

📌 Async-oriented API?
Новые инструменты разработаны с учетом обратной совместимости - работа с TestBed и существующими утилитами.
Одна из приглянувшихся мне фичей - Test Context, которая позволяет задать контекст тестирования. На первый взгляд, похоже на DI для тестов. Будет удобнее работать, например, с фича флагами, передавая конкретный контекст для теста. Есть и другие интересные инструменты - Polling Feature, Soft Assertions.

📌 При чем тут ZoneJS?
Zone.js патчит setTimeout, setInterval и тд, что позволяет отслеживать асинхронные операции. Однако нативные Promise оставались вне зоны контроля. Сейчас есть некоторые проблемы с долгим ожиданием таймеров и непредсказуемым поведением в сложных сценариях.

С 21 версии zoneless является стандартом. Это означает, что
- fakeAsync() и waitForAsync() перестают работать без явного включения Zone.js
- Существующие тесты требуют переписывания асинхронных частей.
Vitest и Jest предлагают собственные механизмы контроля времени:

// Включение fake timers
vi.useFakeTimers();

// Запуск всех таймеров (включая setInterval)
vi.runAllTimers();

// Запуск только ожидающих таймеров
vi.runOnlyPendingTimers();


📌 Недавно был влит MR, добавляющий возможность выбора между vitest (default) и karma и установку соответствующего билдера со всеми зависимостями.

📌 Разработчики предполагают постепенную миграцию. Стоит начать с добавления Vitest параллельно с существующей конфигурацией, затем постепенно переносить наиболее медленные тесты, заменять fakeAsync() на fake timers. И полностью перейти на новую инфраструктуру.

📌 Довольно солидное изменение в экосистеме и подходах к тестированию. С каждым обновлением Angular все больше следует новым трендам и привносит новые технологии! Привычные инструменты уступают место более эффективным, которые меняют опыт разработки.

#angular #angular21

NgStream / Чат
🔥6❤‍🔥11