Миграция с angular на angular2

с середины

Katerina Pavlenko
Aug 9, 2017 · 3 min read

Сейчас я работаю в проекте, который являет собой гибридное приложение в процессе перехода с первого ангуляра(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 и (самое сладкое) юнит-тестов довольно обширные, и я хочу про них написать нечто более развернутое однажды. Есть предложения или конкретные вопросы — жду!


Если вы тоже в процессе миграции и эта статья не помогла разобраться — пишите мне письма или в телеграм, постараюсь помочь.

    Katerina Pavlenko

    Written by

    frontend developer, love super simple explanations of complex things https://github.com/cakeinpanic

    Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
    Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
    Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade