Organizing modules in Angular 2

I started open sourcing a new project called Teki. It's a schedule management tool built on top of Angular 2 and Rails 5, which are also the two new technologies that I'm getting familar with.

The other day, I did a large refactoring to Teki, and I thought I would share my experience in this post.

Teki is based on angular2-seed. It started with a really flat structure, but as I started adding different personas and different permissions for my rails endpoint, it started getting hard to manage.

Three main reasons that prompted me to this refactoring:

  • The code looked repetitive for some services (Ex: every api service had a similar get, update, destroy functions )
  • The services shared really similar names. (Ex: GET /api/users for users vs GET /api/admin/users would both be ShiftService if you decided to have a admin user page and a user page with non-admin access)
  • The api file structure should match to backend:
Since the backend structure looked like this:  
├── api
    ├── admin
    │   └── users_controller.ts
    └── users_controller.ts

The frontend should not look like this:  
├── api
    ├── admin-users.ts
    └── users.ts

Instead, it should be this:  
├── api
    ├── admin
    │   └── users.ts
    └── users.ts

To solve these, I ended up doing the following:

  • Add Base Class
    • To allow the rest of api endpoints to extend
  • Reorganize Export & Import
    • To allow clear namespace such as Api.Users and Api.Admin.Users

Here's how:

Add Base Class

Take an example for an api service. With Typescript, we can create a base class with generic type, which looks like this:

export class ApiBase<TModel> implements IApi<TModel> {  
  baseRoute: string;

  constructor(public authHttp: AuthHttp) {}

  getAll(query = {}): Observable<TModel[]> {...}

  create(data: TModel): Observable<TModel> {...}

  update(data: any): Observable<TModel> {...}

  destroy(data: any): Observable<TModel> {...}
}

The implemntation of getAll, create, update and destroy are the same for all different endpoints, except they have different baseRoute. This means when I create a new api service this is all I need to do:

@Injectable()
export class Employee extends ApiBase<Model.Admin.Employee> {  
  baseRoute:string = API_ENDPOINTS.EMPLOYEES;

  constructor(authHttp: AuthHttp) { super(authHttp); };
}

Swap out the baseRoute, call ApiBase constructor and that's it!

Reorganize Export & Import

The current structure looks similar to this:

├── api
│   ├── admin
│   │   ├── user.ts
│   │   └── index.ts
│   ├── base.ts
│   ├── user.ts
│   └── index.ts
└── index.ts

Where /api/admin/index.ts looks like:

import { User } from './user';

export {  
  User
};

and /api/index.ts looks like:

import * as Admin from './admin/index';  
import { User } from './user';

export {  
  Admin,
  User
};

Now in my application, I can start referencing my api with Api.Admin.User and Api.User. For example in my angular bootstrap function, I can do

import * as Api from './api/index';  
...

bootstrap(AppComponent, [  
  Api.Admin.User,
  Api.User
])

Pros & Cons

Pros:

  • It makes the code more organized
  • A similar structure can apply to interfaces as well. Ex. Model.Admin.User would map to user model with admin access which is usually different than Model.User for non-admin access.
  • When the application gets large, there will definitely be services with similar names. Organizing in this way can help solve naming conflicts.

Cons:

  • It makes the code looks lengthy. Ex: let users: Model.Admin.User[] = [] vs let users: User[] = []
  • When a file that starts with import * as Api from './api/index'; vs import { ApiAdminUser } from './api/index', the later one is much clearer its dependencies.

Thanks for reading, any suggestions/recommendations are welcome : )

comments powered by Disqus