Adopting AngularJS 1.5+ Component Architecture for Modular UI
Step‑by‑step guide to replace a legacy directive with an AngularJS 1.5+ component, including setup, implementation, testing, and rollback.
06 Oct 2025, 21:45 UTC

Desired outcome
Replace a legacy directive with an AngularJS 1.5+ component so the UI piece is self‑contained, reusable, and easier to test.
Necessary prerequisites
- AngularJS version 1.5.0 or later (check with
angular.version.fullin the console). - A build step that respects
controllerAsbindings (e.g.,ng-annotateor Babel plugin). - Git (or another VCS) to track changes and enable rollback.
- Karma/Jasmine test runner set up for unit testing AngularJS code.
- Access to the project’s source files (read/write).
Focused procedure
-
Create the component file
Add a new file
src/components/user-profile/user-profile.component.js.(() => { angular .module('app') .component('userProfile', { bindings: { user: '<', // one‑way binding onUpdate: '&' // expression binding }, transclude: true, // allow projected content templateUrl: 'user-profile.template.html', controller: class UserProfileController { constructor() { 'ngInject'; } $onChanges(changesObj) { if (changesObj.user) { // react to incoming user object this.formattedName = `${this.user.firstName} ${this.user.lastName}`; } } updateUser() { this.onUpdate({ $event: { user: this.user } }); } } }); })();Place the template next to the component:
{{$ctrl.formattedName}}
{{$ctrl.user.email}}
Save -
Register the component module (if not already)
Ensure the
appmodule exists or create it insrc/app.module.js:angular.module('app', []); -
Use the component in a view
Insert the component where you need the UI, passing bindings:
Last login: {{ctrl.currentUser.lastLogin | date}} -
Update build/minification configuration
If using
ng-annotate, ensure it is run on the new component file. For example, add to your Grunt/Gulp/Webpack config:// Grunt ng-annotate task ngAnnotate: { build: { files: [{ expand: true, cwd: 'src', src: ['**/*.js'], dest: 'dist' }] } }Verify that the generated code retains the
controllerAsalias ($ctrl) after minification. -
Write a unit test
Create
src/components/user-profile/user-profile.component.spec.js:describe('userProfile component', () => { let $componentController, bindings; beforeEach(module('app')); beforeEach(inject((_$componentController_) => { $componentController = _$componentController_; bindings = { user: { firstName: 'Ada', lastName: 'Lovelace', email: '[contact removed]' }, onUpdate: jasmine.createSpy('onUpdate') }; })); it('exposes formattedName on $onChanges', () => { const ctrl = $componentController('userProfile', null, bindings); ctrl.$onChanges({ user: { currentValue: bindings.user } }); expect(ctrl.formattedName).toBe('Ada Lovelace'); }); it('invokes onUpdate when Save is clicked', () => { const ctrl = $componentController('userProfile', null, bindings); ctrl.updateUser(); expect(ctrl.onUpdate).toHaveBeenCalledWith({ $event: { user: bindings.user } }); }); }); -
Run tests and verify in the browser
Execute the test suite:
# Run Karma from project root $ npm testStart the dev server and open the page containing the component:
$ npm start # then navigate to http://localhost:8000/dashboard.htmlOpen the browser’s developer tools and:
- Inspect the rendered DOM to confirm the
user-profileelement appears with the expected text. - Check the AngularJS DevTools (or Batarang) pane: the component’s isolate scope should show
user,onUpdate, and$ctrl.formattedName. - Click the Save button and verify that the
onUpdate spy (if you added a temporary logging statement) is called.
- Inspect the rendered DOM to confirm the
Expected checks
- Unit tests pass (no failures, no pending specs).
- The component renders without console errors.
- Bindings update correctly when the parent changes the
userobject. - Transcluded content appears inside the component’s template.
- Minified production build still shows the component working (check the network for the minified JS and ensure no “[$injector:itot]” errors).
Limitations
- Components add an extra file and module registration; in very small apps this may increase bundle size. Mitigate by lazy‑loading feature modules or using a build step that splits code.
- The
controllerAsalias must survive minification; if you rely on automatic annotation, test the minified output or switch to explicit[$inject]arrays. - Component API is unavailable in AngularJS < 1.5; upgrading the framework is a prerequisite.
Recovery options (rollback)
Because the procedure adds new files and modifies existing ones, you can revert the changes:
- If using Git, reset the last commit that introduced the component:
- Or manually delete the added files and remove the component registration from
app.module.js. - After deletion, run the test suite and manual verification to confirm the application returns to its previous state.
$ git reset --hard HEAD~1 # removes the commit
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.