So Google stopped supporting AngularJS … and you’re left with an application that hasn’t been touched in years. Its creators are gone. While the application still works fine … the inside is a tangled mess of hastily added, undocumented, untested code.
Well—breathe deep—because in this post we’re going to follow some repeatable steps you can follow to move your AngularJS application to Angular.
Rewrite or Upgrade?
Is your AngularJS code already pretty well-organized into objects that resemble components and services? Good news!
Angular provides a tool named ngUpgrade that allows you to create a hybrid application. This tool run both runtimes side-by-side. This will affect the performance of your application but will also allow you to upgrade your code piece by piece instead of all at once.
Practically, if you want to keep deploying the same branch and have upgraded code as you go, using ngUpgrade sounds like a good move. This flexible rewrite approach avoids a long lived “upgrade” branch.
However, if your AngularJS code doesn’t map well to Angular concepts, it might be easier to go through the steps I outline below—in a development branch—and rip off the bandage when it comes time to deploy.
Make a “page” component, copy over the template
Older AngularJS applications tend to be set up by page. The AngularJS router has one page per router destination, and everything is jammed into that one page and page controller.
The easiest way to move that over into an Angular application is to mimic it! Start by copying over the whole page template and creating a page-level component.
If you have something like PageController in AngularJS, you can create a page component by using
ng generate component page
Then copy in your HTML into the new page/page.component.html file. I also recommend doing a review and creating stub methods and variables in page.component.ts to suppress errors in the HTML file.
One thing to keep in mind with this: you won’t be able to immediately modularize your Angular application. Most likely, the simplest path is going to be putting everything in your AppModule. This isn’t too dissimilar to the shared scopes for AngularJS, so I don’t think it’s unreasonable to go this route.
Modularizing your AngularJS application is preferable to having everything in one AppModule
because the build system can build smaller artifacts—and only load modules when they are needed. You can and should still revisit this after you’ve reimplemented your application as an Angular application.
Break apart the template into components
Do a close read of the template code and you should be able to identify portions that are related to themselves.
Especially look for HTML that:
- takes in user input (forms, buttons)
- displays information to the user (tables, cards, banners, content blocks)
Each of those should be turned into a component itself.
The goal is to have your user concerns pushed into child components, while your page-level component can concern itself with business logic and communicating with services.
It would also work well to have those child components live together with the corresponding page component. This will help with creating modules later.
This can be accomplished with
ng generate component page/child
And Angular will create the component HTML/CSS/TS files inside! Then move HTML from your page component over to the child component HTML file.
Turn template vars into component inputs and behavior into outputs
Now your component is taking shape! Your page component HTML file will be mostly child component HTML selectors, and your child component will look like regular HTML snippets.
Now you can sweep through and review what functionality is coming from your child components.
An ng-click should be turned into a click event and an Output()
to the page parent component. Data displayed should be turned into Input()
s.
An aside about forms
Angular has two kinds of ways to create forms. Template-driven forms rely mainly on setup in your HTML markup; the form grouping and validation is set in your template. Reactive forms are set up in your component TypeScript, and then hooked into your HTML template.
Most forms written for AngularJS will match the patterns in Angular template-driven forms and you’ll find the least resistance that way.
However, sometimes the reactive form approach will be easier. So it’s worth understanding how reactive forms work. If you used Formly in AngularJS, for example, then you must use reactive forms.
Wire up API at the top component
AngularJS used callbacks, but callbacks could eventually get hard to reason about as you nest callbacks together. Angular requires moving toward Observables, which allows for a callback syntax that requires less nesting. Observables can make your code simpler to reason about.
Your next step after making inputs is going to be populating those inputs with data, often coming from Observables. The Angular HTTP service uses Observables. And if you use the Redux pattern, the Angular library for that—ngrx—also uses Observables.
Angular’s Big Idea: The Async Pipe
One big upshot of using Observables is you can remove code repetition significantly using the async pipe.
Observables aren’t executed until they’re subscribed to, and the async
pipe in your Angular templates will subscribe to the Observable for you instead of writing subscription code in your component class. The async pipe will also handle unsubscribing, and works with the onPush
change detection strategy. Angular uses this to optimize changes to the component tree.
Using the async pipe, you can drive UI behavior, and feed data into your child components without having to write the subscription code into your class. The async pipe can be used in a *ngIf
to drive a little bit of conditional logic. This is especially useful with the ng-container
element, because it can be used with ng-template
to change what’s displayed based on whether the Observable has data or not. The async pipe can be used in a *ngFor
to feed an array into your template, or a component input to send that data where it’s more useful.
Rinse and repeat
Now keep going through this process with each top level page in your AngularJS app, and keep an eye out for services, pipes, and other Angular features you can use. Before you know it, you’ll have a whole new application!
What about dependencies?
Good luck 🤷! Some libraries have maintained compatibility after the ng2 change, some didn’t.
There are replacements for most common cases of UI components. If you aren’t doing a full redesign to Google’s Material UI, you should still familiarize yourself with Google’s Material UI CDK. The CDK serves as a library of unstyled components that implement UI behaviors in Material UI, and you can style these to match your project.