Migrate PersonCreateComponent

Important

The source code for this course can be found on GitHub. Each step has it’s own branch, instructions for how to checkout the correct code for each step are in the Project Setup lecture.

Migrate PersonCreateComponent

In this lecture we are going to work on the person-create component and migrate it from AngularJS to Angular. So lets get started!

Converting our list component to Angular

The code for our person-create component resides in the person-create.component.ts file which is shown below.

Listing 1. person-list.component.ts
import * as angular from 'angular';

export let PersonCreateComponent = {
  selector: 'personCreate',
  template: `
<div class="col-md-8 col-md-offset-2">
  <form class="form-horizontal"
        ng-submit="$ctrl.save()"
        novalidate>
    <div class="panel panel-default">
      <div class="panel-heading">
        Create
        <div class="pull-right">
          <button class="btn btn-primary btn-sm"
                  ladda="$ctrl.contacts.isSaving"
                  type="submit">Create
          </button>
        </div>
        <div class="clearfix"></div>

      </div>
      <div class="panel-body">
        <ng-include src="'templates/form.html'"></ng-include>
      </div>
    </div>
  </form>
</div>
`,
  bindings: {},
  controller: class PersonCreateController {
    public contacts = null;
    public person = {};

    private $state = null;

    constructor($state, ContactService) {
      this.$state = $state;
      this.contacts = ContactService;
      this.person = {};
    }

    save() {
      console.log("createContact");
      this.contacts.createContact(this.person)
          .then(() => {
            this.$state.go("list");
          })
    }
  }
};

angular
    .module('codecraft')
    .component(PersonCreateComponent.selector, PersonCreateComponent);

If you look at the controller of the PersonCreate component, you may notice that the component uses the UI-router’s $state service. This will be the first instance where we deal with UI-router in our application, and we will see how we can handle it during our conversion process.

Lets first start by converting this file into a class-based Angular implementation like we have done with our previous components.

Creating the PersonCreateComponent class

  • Take the code from the controller property and move it to a separate PersonCreateComponent class.

  • Manually inject the ContactService using the @Inject decorator. (For now, lets remove the $state from the constructor. We will deal with it shortly)

  • Then, add the @Component decorator to this newly created PersonCreateComponent class using the selector and template properties.

Your person-create.component.ts file should now look like so:

import * as angular from 'angular';
import { Inject, Component } from "@angular/core";
import { ContactService } from "../services/contact.service";

@Component({
  selector: 'personCreate',
  templateUrl: `
    <div class="col-md-8 col-md-offset-2">
      <form class="form-horizontal"
            ng-submit="$ctrl.save()"
            novalidate>
        <div class="panel panel-default">
          <div class="panel-heading">
            Create
            <div class="pull-right">
              <button class="btn btn-primary btn-sm"
                      ladda="$ctrl.contacts.isSaving"
                      type="submit">Create
              </button>
            </div>
            <div class="clearfix"></div>

          </div>
          <div class="panel-body">
            <ng-include src="'templates/form.html'"></ng-include>
          </div>
        </div>
      </form>
    </div>
  `
})
export class PersonCreateComponent {
    public person = {};

    constructor(@Inject(ContactService) public contacts: ContactService) {
      this.person = {};
    }

    save() {
      console.log("createContact");
      this.contacts.createContact(this.person)
          .then(() => {
            this.$state.go("list");
          })
    }
  }

angular
    .module('codecraft')
    .directive('personCreate', downgradeComponent({
      component: PersonCreateComponent
    }));

Next, add this newly created PersonCreateComponent to the declarations and entryComponents properties of the NgModule like so:

...
import { PersonCreateComponent } from "./components/person-create.component";
...

@NgModule({
  imports: [
    ...
  ],
  providers: [
    ...
  ],
  declarations: [
    SearchComponent,
    DefaultImagePipe,
    CardComponent,
    SpinnerComponent,
    PersonListComponent,
    PersonCreateComponent
  ],
  entryComponents: [
    SearchComponent,
    CardComponent,
    SpinnerComponent,
    PersonListComponent,
    PersonCreateComponent
  ]
})
...

Modifying the template code

Consider the template code of our person-create component:

<div class="col-md-8 col-md-offset-2">
  <form class="form-horizontal"
        ng-submit="$ctrl.save()"
        novalidate>
    <div class="panel panel-default">
      <div class="panel-heading">
        Create
        <div class="pull-right">
          <button class="btn btn-primary btn-sm"
                  ladda="$ctrl.contacts.isSaving"
                  type="submit">Create
          </button>
        </div>
        <div class="clearfix"></div>
      </div>
      <div class="panel-body">
        <ng-include src="'templates/form.html'"></ng-include>
      </div>
    </div>
  </form>
</div>

The above template uses the ng-include directive to fetch and include external HTML code into the template. However, there is no equivalent in Angular. Also note that this same form template is used by the person-edit component.

Hence to avoid code duplication, what we can do is share a common form template between the person-create and person-edit components.

Create the following person-form.html file in src/app/components like so:

<div class="col-md-8 col-md-offset-2">
  <form class="form-horizontal"
        (ngSubmit)="save()"
        novalidate>

    <div class="panel panel-default">
      <div class="panel-heading">

        {{mode}}

        <div class="pull-right">
          <button class="btn btn-primary btn-sm"
                  [ladda]="contacts.isSaving"
                  type="submit">
            <span>Save</span>
          </button>

          <button class="btn btn-danger btn-sm"
                  [ladda]="contacts.isDeleting"
                  *ngIf="mode === 'Edit'"
                  (click)="remove()">Delete
          </button>
        </div>
        <div class="clearfix"></div>

      </div>
      <div class="panel-body">

        <div class="form-group">
          <label class="col-sm-2 control-label">Name</label>
          <div class="col-sm-10">
            <input type="text"
                   class="form-control"
                   name="name"
                   [(ngModel)]="person.name"
                   required />
          </div>
        </div>
        <div class="form-group">
          <label class="col-sm-2 control-label">Email</label>
          <div class="col-sm-10">
            <input type="email"
                   name="email"
                   class="form-control"
                   [(ngModel)]="person.email"
                   required />
          </div>
        </div>
        <div class="form-group">
          <label class="col-sm-2 control-label">Photo</label>
          <div class="col-sm-10">
            <input type="text"
                   class="form-control"
                   name="photo"
                   [(ngModel)]="person.photo"
            />
          </div>
        </div>
        <div class="form-group">
          <label class="col-sm-2 control-label">Sex</label>
          <div class="col-sm-10">

            <select name="sex"
                    class="form-control"
                    [(ngModel)]="person.sex"
                    id="">
              <option value="M">Male</option>
              <option value="F">Female</option>
            </select>
          </div>
        </div>
        <div class="form-group">
          <label class="col-sm-2 control-label">Birthday</label>
          <div class="col-sm-10">
            <input type="date"
                   name="bday"
                   class="form-control"
                   [(ngModel)]="person.birthdate "
            />
          </div>

        </div>
        <div class="form-group">
          <label class="col-sm-2 control-label">Phone</label>
          <div class="col-sm-10">
            <input type="tel"
                   name="phone"
                   class="form-control"
                   [(ngModel)]="person.phonenumber"
            />
          </div>

        </div>
        <div class="form-group">
          <label class="col-sm-2 control-label">Address</label>
          <div class="col-sm-10">
            <input type="text"
                   name="address"
                   class="form-control"
                   [(ngModel)]="person.address"
            />
          </div>

        </div>

        <div class="form-group">
          <label class="col-sm-2 control-label">City</label>
          <div class="col-sm-10">
            <input type="text"
                   name="city"
                   class="form-control"
                   [(ngModel)]="person.city"
            />
          </div>

        </div>
        <div class="form-group">
          <label class="col-sm-2 control-label">Country</label>
          <div class="col-sm-10">
            <input type="text"
                   name="country"
                   class="form-control"
                   [(ngModel)]="person.country"
            />
          </div>
        </div>
      </div>
    </div>
  </form>
</div>

This is just a standard Angular template-driven form that will be shared between our person-create and person-list components. It has a special property called mode that determines if the form should be a create form or an edit form.

To use this form in our person-create component, replace the in-line template code with the person-form.html file’s path like so:

...
templateUrl: 'app/components/person-form.html'
...

Downgrading the Search component

To maintain compatibility, we will need to downgrade our PersonCreateComponent. There’s nothing new here, just follow the same procedure like before.

In person-create.component.ts, import the downgradeComponent function like so:

import { downgradeComponent } from "@angular/upgrade/static";

and modify the component registration code like so:

angular
    .module('codecraft')
    .directive("personCreate", downgradeComponent({
        component: PersonCreateComponent
    }));

The UI Router’s $state service

UI-Router is the defacto standard for routing in AngularJS. Lets see how we can temporarily upgrade the UI-Router $state service, so that it can be used within our Angular component. Later on when we convert our application to Angular, we will replace this with Angular’s in-built routing service.

Just like we did with our toaster module, we can temporarily upgrade our UI-Router’s $state service. add the following code to the ajs-upgraded-providers.ts file:

...
export const UIRouterState = new InjectionToken("UIRouterState");

export function uiRouterStateServiceFactory(i: any) {
  return i.get('$state');
}
export const uiRouterStateProvider = {
  provide: UIRouterState,
  useFactory: uiRouterStateServiceFactory,
  deps: ['$injector']
};
...

Just like in our toaster module, the InjectionToken creates a token that can be used in a DI provider. The provider then returns the $state from our AngularJS injector.

To ensure that we provide this within our application, add it to the list of providers in the NgModule like so:

import { toasterServiceProvider, uiRouterStateProvider } from "./ajs-upgraded-providers";
...
@NgModule({
  imports: [
    ...
  ],
  providers: [
    Contact,
    ContactService,
    toasterServiceProvider,
    uiRouterStateProvider
  ],
  declarations: [
    ...
  ],
  entryComponents: [
    ...
  ]
})
...

Finally, to inject this new version of the $state provider, into our person-create component, modify the PersonCreateComponent 's constructor like so:

...
    constructor(@Inject(ContactService) public contacts: ContactService, @Inject(UIRouterState) private $state) {
      this.person = {};
    }
    ...

With this, we complete the migration of our person-create component from AngularJS to Angular! Be sure to rebuild and run the application on localhost to verify that everything works as expected.


Caught a mistake or want to contribute to the book? Edit this page on GitHub!


Learn Angular For FREE

I've released my 700 page Kick Starter funded Angular book for FREE