Hello developer! Maybe you think that for migrating from AngularJS to Angular, you have to learn a whole new framework, a new structure of work and things like that. But thank God, the IT world has turned out to be more convenient and developer-friendly. In this article ‘Migrating Angular JS to Angular Without Losing Your Head’ I have collected the main points on planning, problem solving and new working conventions, and decided to share them with you. I hope that these notes will be useful for those who have started migrating or are about to do so. The peculiarity of this article is that the information will not turn your head, because everything is written clearly and understandably. Let’s start!
Why do we need this?
Firstly, Angular is better than AngularJS in everything – it is faster, lighter, more convenient, it has fewer bugs (for example, template typing helps to fight them). Much has been said and written about this, it makes no sense to repeat myself. This was understandable since Angular 2, but a year ago it was scary to start a transition: what if Google again decides to turn everything upside down with the next version, without backward compatibility? The transition to an essentially new framework requires serious resources, and we do not want to do it every two years. Angular 4 gives hope that there will be no more revolutions, which means it’s time to migrate. Secondly, we need to update the technologies used in our platform. If this is not done according to the principle “if something is not broken, it is not necessary to fix it”, at some point we will cross the line beyond which further progress will be possible only if the platform is rewritten from scratch. Sooner or later, you will have to switch to Angular anyway, but the earlier you do it, the cheaper the switch will be (the amount of code is growing all the time, and we will get the benefits of the new technology earlier). Finally, the third important reason: developers. AngularJS is a passed stage, it fulfills its tasks, but it does not develop and will never develop; our platform is constantly growing. Strong developers are always interested in new technologies, they are simply not interested in dealing with an outdated framework. The transition to Angular makes our vacancies more interesting for strong candidates; in the next two or three years they will be quite relevant.
How to migrate?
You can make the migration in parallel – the platform runs on AngularJS, we write from scratch and test the new version, and at a certain moment we just switch the toggle switch. The second option is a hybrid mode, when changes take place directly in production, where both AngularJS and Angular are running at the same time. Fortunately, this mode is well thought out and well documented. The choice between hybrid and parallel transition modes depends on how actively the product is evolving. The choice of mode depends on whether you can afford that luxury. During the preparation of the new version, all development stops in a parallel transition, and no matter how competently we calculate the time of the move, there is a possibility that the process will drag on, we will bump into something and generally will not understand what to do next. In hybrid mode, in this situation, we can just stop and calmly look for a solution, since we still have an up-to-date working version in production; it may not work as efficiently and a little harder, but no processes are stopped. In parallel, we would have had a rollback with corresponding losses. In general, it should be borne in mind that a hybrid transition is not a force majeure, it is a completely normal, default procedure according to the developers of Angular itself; there is no need to be afraid of him.
The sequence of actions should look something like this:
1. Hybrid app initialization:
Bootstrap angular, which bootstrap angularjs. Everything remains as it was, only now we are going slower and running longer (while the hybrid mode is working). It is no longer possible to throw the controller on the head, all work with the title / favicons / meta tags is taken out to services that directly interact with the necessary elements in the head.
2. Porting services to Angular:
The easiest. The rewritten services are quickly made available from AngularJS, which the components are still running on. Starting with the simplest ones, without dependencies, to more complex ones.
3. Drawing the rest of the owl:
Transferring the basic components (GUI and everything else that does not use other components / directives). We transfer the components from bottom to top, as modularly as possible.
4. Combing the feathers:
Transferring the page components, polishing AngularJS.
Now, finally, move on to the technical details. These are not universal solutions at all, but maybe they will help someone to solve emerging problems. How to transfer individual elements?
- If there is no Angular module in the module in which we are starting to upgrade something, then we create it and attach it to the main application module. If the AngularJS module is still alive, then the new one is named with the new postfix. We cut out the postfix together with the old AngularJS module.
- In a good case, add a decorator, remove default from export, edit imports (since the default was removed), import into the Angular module, downgrade in the AngularJS module. The service remains available under the old name in AngularJS and does not require additional configuration. A good option implies: all injection services have already moved to Angular, no specific things like templateCache or compiler are used. In 95% of cases, we suffer, first upgrading what is injected, getting rid of all sorts of strange AngularJS services, etc.
- We drop a decorator with metadata to the controller, add decorators to inputs / outputs and transfer them to the beginning of the class. All injected services, all require components and all components / directives / filters used inside the template must be on Angular. All component variables used in the template must be public. If the component receives all the data for output from the component above (via inputs), then feel free to write to it in the meta-data changeDetection: ChangeDetectionStrategy.OnPush. This tells Angular that it will sync the template with data (let it change detection for this component) only if any of the component’s inputs change. Ideally, most of the components should be in this mode (but it is unlikely in our case, since very large components receive data for output through services).
- Same as component, only no template and @Directive decorator. It is thrown into the module there, it is necessary to export it for use in the components of other modules in the same way.
- Now he is @Pipe and must implement the PipeTransform interface. It is thrown into the module in the same place as the components / directives, and must also be exported if used in other modules. Angular directives and filters cannot be used in AngularJS component templates and vice versa. Only services and components are forwarded between frameworks.
- Exports / Imports and Interfaces. First, get rid of the export default. Secondly, due to the current structure of modules (very large) and the use of interfaces (we put a bunch of them in the same file where the classes are), we caught a funny bug with the import of such interfaces and their use with decorators: if the interface is imported from a file containing exports not only interfaces, but also, for example, classes / constants, and such an interface is used for typing next to the decorator (for example, @Input () smth: ISmth), then the compiler will issue an import error export ‘ISmth’ was not found. This can be fixed either by moving all interfaces into a separate file (which is bad because of large modules, such a file will be a dozen screens), or by replacing interfaces with classes. Replacing them with classes will not work, because they cannot be inherited from multiple parents. The best solution is to create an interface directory in each module, which will contain files with entity names that contain the corresponding interfaces (for example, room, step, content, workbook, homework). Accordingly, all interfaces that are not used locally are put there and imported from such file directories. The best solution is to create an interface folder in each module, which will contain files with entity names that contain the corresponding interfaces (for example, room, step, content, workbook, homework). Accordingly, all interfaces that are not used locally are put there and imported from such file folders.
Features (transclude, parameter transfer, svg import)
- If the upgraded component uses the transclude (ng-content), then when using the component from the AngularJS templates: multi-slot transclude do not work, only the ability to forward everything in one piece through one ng-content; ui-view cannot be dropped into the transclude of such a component, because it won’t work (broken off when trying to upgrade the viewport component); if a component is used in this way, then either we postpone its upgrade until all the places where it is used are upgraded, or we make a copy of it for parallel operation in already upgraded components.
- When using the Angular component in the AngularJS component, the inputs are written as for a regular Angular component (using  and ()), but in kebab-case. When rewriting such a template for Angular, we edit kebab-case to camelCase.
- We transfer the svg import to the ts file and pass it through the component property.
Dynamic components and templates
$compile is no longer there, just as there is no compilation from a string (in fact, there is a small hack, but here is about how to live 95% of the time without $compile). The class of the inserted component can be passed through service, inputs, or something else. vim-base-dynamic-component is an already written component for dynamically inserting other components with support for inputs / outputs (in the future, if needed).
If you need to display different templates by condition, and for this you use a dynamic templateUrl, replace it with a structural directive and break the component into three (request / data processing; display for mobile phones; display for desktops). The first component has a minimal template and is engaged in working with data, processing user actions, and the like (such a template, because of its brevity, it makes sense to put right there in the template of the component’s property via “ instead of a separate html file and templateUrl). Mobile and desktop components receive data via inputs, send some events via output and only output what is needed. All complex logic, processing, changing data – in the main component that outputs them. In such components (desktop / mobile), you can safely register changeDetection: ChangeDetectionStrategy.OnPush
The migration is a natural procedure, well prepared and described by the Angular team in the official documentation. Nevertheless, in practice, difficulties and gaps arise that have to be solved on the fly. In the article ‘Migrating Angular JS to Angular Without Losing Your Head’ I talked about the problems that every developer may face and shared some solutions to them. Hope it was really useful and interesting for you! Thanks for reading.