Миграция с angular на angular2
с середины
Сейчас я работаю в проекте, который являет собой гибридное приложение в процессе перехода с первого ангуляра(1.5.8) на второй(2.2.0).
Миграция пришла в проект давно, а я — недавно, и второй ангуляр не видела никогда.
Когда резко окунаешься во что-то новое, очень не хватает простых обстоятельных советов. Иначе гуглишь-гуглишь нюансы, объяснения, причины, следствия. А нужно просто факт найти.
Это маленькое и простое руководство для тех, кто тоже мигрирует.
Intro
Официальное руководство на английском

Вы переписываете компонент на ng2, а внутри него используются ng1-директивы или компоненты.
Нет, они не будут работать. Только если передать их трансклюзивно и вставить через ng-content
myComponent.ts@Component({
selector: 'my-component',
template: `<div class="my-component">
<ng-content></ng-content>
</div>`
})someTemplate.ng1.html<my-component>
look, wow: <ng1-component/>
</my-component>output:<my-component>
<div class="my-component">
look, wow: <ng1-component/>
</div>
</my-component>
Вы переписываете компонент или сервис на ng2, а внутри него используются ng1-сервисы
Пока мы разрабатывали, upgradeAdapter уже успел стать deprecated, но вдруг у вас та же версия ангуляра
upgradeAdapter.upgradeNg1Provider('MyService');@Component({
selector: 'my-component',
template: '<div></div>'
})
export class MyComponent {
// важно, что имя – не объект, а строка
constructor(@Inject('MyService') private myService: MyService) { }
}
Как правильно делать @Inject в ng2-конструкторе
Если подключается заимпорченный ng2-сервис, используется ссылка на него. Если ng1-сервис, то используется строка.
myNg2Component.ts
import {MyNg2Service} from './myService.ts';constructor(@Inject('$rootScope') private $rootScope,
@Inject(MyNg2Service) private myService) {
}
Используется старый ui-router, а ui-view больше не работают
ui-view это директива, так что смотри первый пункт. Решение такое:
router.ts
.state('index.page', {
url: 'page',
template: '<page-component><ui-view></ui-view></page-component>'
})
.state('index.page.details', {
url: '/details',
template: '<details></details>'
})pageComponent.ts@Component({
selector: 'page-component',
template: '<div>
<h1>Page title comes here.</h1>
And page details here: <ng-content></ng-content>
</div>'
})details.ts@Component({
selector: 'details',
template: '<div>page details come here {{details}}</div>'
})
Используется старый ui-router и ui-sref перестали работать
так как ui-sref тоже суть директива из первого ангуляра, она отвалилась. Используем (сlick) и $state.go в обработчике. В этом примере мы лишились способности ссылки открываться в новом окне, но это тоже легко поправимо при необходимости.
someTemplate.ng1.html
<a ui-sref="home">Home</a>someTemplate.ng2.html
<a (click)="goHome($event)">Home</a>someComponent.ts
goHome(event) {
event.preventDefault();
this.$state.go('home');
}
Вы переписали сервис на ng2, но другой ng1-сервис наследует его
Так работать не будет. Решение: сервис суть обычный класс. Оформить базовый сервис как класс, наследовать и ng2- и ng1-сервисы от этого базового класса.
Вы переписали сервис/компонент/директиву на ng2, но он все еще используется в ng1-модулях
Сервисы — в фабрики, компоненты — в директивы, детям — мороженое.
Важно, в какой нотации писать имя новоявленной директивы: всегда пишем в camelCase, в snake-case он преобразуется автоматически
angular
.module('ng1Module', [])
.factory('MyMigratedService', // my-migrated-
upgradeAdapter.downgradeNg2Provider(MyMigratedService)
)
// будет <my-migrated-component></my-migrated-component>
.directive('myMigratedComponent',
upgradeAdapter.downgradeNg2Component(MyMigratedComponent) as ng.IDirectiveFactory)Биндинг
При использованиии ng-2 компонентов в ng-1, биндинг производится не через camelCase, а через kebab-case
// ng1 template
<ng2-component
[one-attribute]="$ctrl.someValue"
(some-output)="$ctrl.listener()"
>
</ng2-component>// ng2 template
<ng2-component
[oneAttribute]="someValue"
(someOutput)="listener()"
>
</ng2-component>
У вас использовался $compile шаблонов
Если вы генерировали разметку динамически при помощи $compile(например, создавали ячейки в таблице), это больше не работает.
Есть множество способов для динамической генерации компонентов, коллега подсказал самый простой:
Слишком большой и сложный сниппет для медиума, оформила в gist.
Внимание, для генерации больших и разрозненных объемов данных(например, таблиц), есть куда лучший способ. Но о нем когда-нибудь попозже
У вас используется $rootScope.$emit, $rootScope.$broadcast, $rootScope.$on
Никакого $rootSсope(и тем более $scope) больше нет. Для этих целей теперь стоит использовать EventEmitter. Или rxjs-Subject. Если необходимо общение между сильно разнесенными по приложению компонентами, нужно создать сервис для необходимых событий, через который можно подписываться на них.
Хочется использовать кастомные атрибуты для нативных элементов
Если необходимо обычному элементу добавить кастомный атрибут, а ошибку EXCEPTION: Template parse errors: Can’t bind to ‘custom-attr’ since it isn’t a known native property получать не хочется, решение такое
<div [attr.custom-attr]="customId"></div>
// если это просто строка
<div [attr.custom-attr]="'customId'"></div> // неправильно
<div custom-attr="someString"></div> // правильно!
Про правильно-неправильно: работать будут оба варианта, но официальная дока ангуляра предлагает придерживаться такой нотации
Темы динамической компиляции шаблонов, работы с rxjs и (самое сладкое) юнит-тестов довольно обширные, и я хочу про них написать нечто более развернутое однажды. Есть предложения или конкретные вопросы — жду!
Если вы тоже в процессе миграции и эта статья не помогла разобраться — пишите мне письма или в телеграм, постараюсь помочь.
