BACK

Moving big parts to Angular 2

4 min read

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.