-Джон Бойд
Первоначально размещенный на https://medium.com/@unbreakabler, разработчик Biznas Джон Бойд делится своим опытом перехода с Angular 1.x на Angular 2, а также проблемами порядка и времени инициализации, с которыми он столкнулся.
В настоящее время мы находимся в процессе преобразования большого (сотни экранов) приложения Angular 1.x в Angular 2. В ходе этого процесса мы столкнулись со многими проблемами, которых не ожидали.
Просмотр документации дает вам простые примеры того, как использовать upgradeAdapter. Все наши разработчики прочитали его, и мы подумали, что хорошо понимаем, как будет проходить процесс.
Это было не так просто, как мы ожидали.
Мы начали с загрузки нашего приложения с помощью Angular 2. Хорошо документированная и простая вещь для работы. Затем мы рассмотрели, что имеет смысл делать дальше, и решили полностью преобразовать наши сервисы Angular 1 в сервисы Angular 2, это было бы намного проще, чем преобразование компонентов/директив, и нам показалось, что это хорошее место для начала.
Здесь мы столкнулись с нашим первым серьезным препятствием, нам пришлось преодолеть проблемы с порядком и временем инициализации. Внедрение зависимостей в компоненты/сервисы Angular 1 и Angular 2 вовсе не было простым.
В частности, мы преобразовали все наши сервисы из сервисов javascript Angular 1.5 в сервисы Typescript Angular 2. Это привело к тому, что мы столкнулись с проблемами, которые редко упоминаются в чьем-либо процессе преобразования:
Инжектор Angular 1 $ готов раньше, чем инжектор Angular 2. Это означает, что любые компоненты/службы/директивы, написанные на Angular 1 и зависящие от службы Angular 2, потерпят неудачу, если они вызовут эту службу Angular 2 в первом цикле приложения.
Скорее всего, вы увидите такую ошибку:
Cannot read property 'injector' of null
Эта ошибка означает, что инжектор не был готов к службе, когда он использовался для внедрения зависимостей при первоначальной настройке приложения. В зависимости от того, где и как вы используете службу Angular 2, вы не всегда можете получить такую идентифицируемую ошибку, как эта, возможно, внедренная зависимость на самом деле не вернет ошибку и будет просто неопределенной переменной.
Если вы хотите отследить путь зависимости этой ошибки, вы можете изменить функцию вызова в angular.js
в строке 4695
, чтобы вывести путь всякий раз, когда в функции вызова возникает ошибка. Вот как мы решили это сделать.
function invoke(fn, self, locals, serviceName) { if (typeof locals === 'string') { serviceName = locals; locals = null; } var args = injectionArgs(fn, locals, serviceName); if (isArray(fn)) { fn = fn[fn.length - 1]; } if (!isClass(fn)) { // http://jsperf.com/angularjs-invoke-apply-vs-switch // #5388 try { // if (path[0] === 'cadAlerts') // console.warn(path.join(' <- ')); return fn.apply(self, args); } catch (err) { if (path.length && path[0] !== 'ng2.Injector') { console.error(err.toString() + " --- " + path.join(' <- ')); } return {}; } } else { args.unshift(null); return new (Function.prototype.bind.apply(fn, args))(); } }
В сочетании с этим протоколированием ошибок try/catch мы добавили сообщение в @angular/upgrade/src/upgrade_adapter.js
, чтобы показать, когда инжектор Angular 2 готов.
@ строка 406 console.warn('NG2 INJECTOR READY');
С помощью этого журнала вы сможете легко увидеть, какие зависимости пытаются внедрить до того, как инжектор Angular 2 будет готов, отследить их и легко внести изменения, необходимые для их функционирования.
Теперь, когда вы отследили, где находятся ошибки внедрения, вы можете обойти проблемы, заключив внедрение зависимостей в тайм-аут, это вызовет инициализацию зависимостей Angular 2 в очереди, и они будут внедрены после Angular 2. инжектор готов.
Вместо :
function ng1Controller(ng2Service) { ng2Service.call(); }; ng1Controller.$inject =['ng2Service'];
У вас будет:
function ng1Controller($injector) { var ng2Service; setTimeout(function() { ng2Service = $injector.get('ng2Service'); ng2Service.call(); } } ng1Controller.$inject =['$injector'];
Теперь ваш ng2Service будет доступен для использования после запуска setTimeout. Но теперь вы столкнетесь с другой проблемой синхронизации. Если вызывается функция, зависящая от ng2Service, которая выполняется до истечения времени ожидания, ng2Service будет недоступен для использования.
Например, если у вас есть какая-то функция инициализации, которая автоматически срабатывает в контроллере или, возможно, вызывается из внешнего контекста (например, .run()), служба будет недоступна.
function ng1Controller($injector) { var ng2Service; setTimeout(function() { ng2Service = $injector.get('ng2Service'); } init(); function init() { ng2Service.initialize(); } } ng1Controller.$inject =['$injector'];
Становится:
function ng1Controller($injector) { var ng2Service; setTimeout(function() { ng2Service = $injector.get('ng2Service'); init(); } function init() { ng2Service.initialize(); } } ng1Controller.$inject =['$injector'];
Это немного надуманный пример. Я просто хочу, чтобы вы усвоили, что вам нужно очень внимательно относиться к порядку, в котором вызываются ваши функции.
Реальный пример этой проблемы произошел с тем, как мы использовали разрешения на определенных экранах. По сути, они вызывали общий базовый класс, который, в свою очередь, вызывал определенный сервис Angular 2 в зависимости от того, откуда он был вызван. Эти разрешения всегда будут выполняться до того, как инжектор Angular 2 будет готов.
Решение этого было точно таким же, как я показал выше. Мы завернули каждое из наших решений в тайм-аут и вернули его.
resolve: { ng2: /*@ngInject*/ function (api) { return api.useNg2Service(); } }
Становится:
resolve: { ng2: /*@ngInject*/ function(api, $timeout) { return $timeout(function() { return api.useNg2Service(); }) } }
Порядок инициализации вашего приложения — это то, чему вам придется уделять много внимания и помнить об этом в течение всего процесса преобразования.
Следуйте за нами, чтобы узнать больше о разработке Angular 2.