Moving big parts to Angular 2
In a previous post I explained how I converted Jangouts to a hybrid Angular 1+2 application. This approach, instead of a full migration, has two objectives. First, testing functionality becomes easier since Jangouts remains runnable throughout. Second, if I cannot finish the migration, others can continue the work. I hope this fallback proves unnecessary.
With the hybrid approach in place, this week I migrated several components to Angular 2. I started with the Chat component---more complex than the Footer, but manageable for these early stages.
Migrating subcomponents
The Jangouts Chat component has three subcomponents besides the main component:
chat-message: Renders user messages.log-entry: Renders system notifications (like "User X has joined").chat-form: Handles message input.
These subcomponents are simple: each has a minimal component class and a
template. The key change: styles moved from the main scss file to independent
files for each subcomponent. This leverages
Angular 2 View Encapsulation,
ensuring styles apply only to their component.
During the chat-message migration, I encountered a problem with
ngEmbed, the library providing a
directive to render user messages. This directive enables emojis and embedded
links, images, and videos. The library lacks Angular 2 support, so I tried the
Angular 2 Upgrade Adapter,
but encountered a strange error.
Investigation revealed that ngEmbed uses a function as its templateUrl
attribute (allowed in Angular 1). However, my current Angular 2 version's
upgrade adapter lacks support for function-based templateUrl. The Angular 2
master branch includes this support, but no released version incorporates it
yet. After discussing with my mentors, we decided to disable this functionality
and continue the migration.
I hope to re-enable it in the future.
Differentiate between component and directive
Migrating the main component proved more complex. It displays all messages (user and system) in a view that auto-scrolls when new messages arrive. In old Jangouts, one directive both rendered the message list and controlled auto-scrolling. Angular 2 requires a different approach: components always have templates and never interact with the DOM directly; directives never have templates but can interact with the DOM.
This meant splitting the main chat component into two parts:
- A component to render the message list.
- A directive to handle auto-scrolling.
After migration, the component renders the message list and contains the directive that handles auto-scrolling.
Putting all together
During subcomponent migration, I downgraded each one to Angular 1 compatibility using Angular 2's adapter and tested manually with the old main component. When I migrated the main component, its code became pure Angular 2 (without downgraded subcomponents). Only the main chat component needed downgrading for Angular 1 compatibility.
Applying the correct application structure
This week's changes extended beyond code. I also restructured the application following the style guide recommendations. Before migration, the structure was:
src└── app├── adapter.ts├── variables.scss├── index.scss├── vendor.scss├── index.ts├── components│ ├── chat│ │ ├── chat-form.directive.html│ │ ├── chat-form.directive.js│ │ ├── chat.directive.html│ │ ├── chat.directive.js│ │ ├── chat-message.directive.html│ │ ├── chat-message.directive.js│ │ ├── log-entry.directive.html│ │ └── log-entry.directive.html│ ├── footer│ │ ├── footer.directive.html│ │ └── footer.directive.js│ └─ ─ [...]└── [...]
After the changes:
src└── app├── adapter.ts├── variables.scss├── index.scss├── vendor.scss├── index.ts├── chat│ ├── index.ts│ ├── chat.component.html│ ├── chat.component.scss│ ├── chat.component.spec.ts│ ├── chat.component.ts│ ├── chat-form│ │ ├── chat-form.component.html│ │ ├── chat-form.component.spec.ts│ │ ├── chat-form.component.ts│ │ └── index.ts│ ├── chat-message│ │ ├── chat-message.component.html│ │ ├── chat-message.component.scss│ │ ├── chat-message.component.spec.ts│ │ ├── chat-message.component.ts│ │ └── index.ts│ ├── log-entry│ │ ├── index.ts│ │ ├── log-entry.component.html│ │ ├── log-entry.component.spec.ts│ │ └── log-entry.component.ts│ └── message-autoscroll.directive.ts├── footer│ ├── footer.component.html│ ├── footer.component.scss│ ├── footer.component.spec.ts│ ├── footer.component.ts│ └── index.ts├── components│ └── [...] // This contains the not migrated code└── [...]
Currently working
I am now migrating the Feed component, one of the most complex in the application due to its many services handling video/audio streams.
I have moved all services and factories to Angular 2, but have not yet enabled Angular 1 compatibility. The reason: I want a comprehensive test suite covering these services before continuing the migration and integrating with the rest of the application.