zipper
zipper

Reputation: 397

Sub state bindings in ui-router/sample-app-angularjs

I'm studying the sample app of UI-Router and got the question.

In sample-app-angularjs/app/contacts/editContact.component.js line71~74 it has:

export const editContact =  {
  bindings: { pristineContact: '<' },

  controller: EditContactController,

In sample-app-angularjs/app/contacts/contacts.states.js line29~38 it has:

export const viewContactState = {
  name: 'contacts.contact',
  url: '/:contactId',
  resolve: {
    // Resolve the contact, based on the contactId parameter value.
    // The resolved contact is provided to the contactComponent's contact binding
    contact: ['Contacts', '$transition$', (Contacts, $transition$) => Contacts.get($transition$.params().contactId)]
  },
  component: 'contactView'
};

And In sample-app-angularjs/app/contacts/contacts.states.js line49~61 it has:

export const editContactState = {
  name: 'contacts.contact.edit',
  url: '/edit',
  views: {
    // Relatively target the grand-parent-state's $default (unnamed) ui-view
    // This could also have been written using ui-view@state addressing: $default@contacts
    // Or, this could also have been written using absolute ui-view addressing: !$default.$default.$default
    '^.^.$default': {
      bindings: { pristineContact: "contact" },
      component: 'editContact'
    }
  }
};

My questions are on the parameter pristineContact:

  1. How it is been passed from state contacts.contact to contacts.contact.edit and then to component editContact? I understand the resolve contact in state contacts.contact is directly available in it's child state contacts.contact.edit, but I cannot find any knowledge-base document related to the clause 'bindings: { pristineContact: "contact" }' (line57) of contacts.states.js. I GUESS it passes the resolve down to it's component(s) so it's component(s) can use "bindings{...}" to input it.

  2. I did not see in the template of component editContact it is used/referenced, but $ctrl.contact is used/referenced instead. -- I cannot understand why the name "pristineContact" is there at all.

Upvotes: 1

Views: 101

Answers (1)

Chris T
Chris T

Reputation: 8216

In the sample app, the editContact component uses dirty checking to determine if a contact has been edited or not. Dirty checking is used to prompt the user if they want to discard their changes if they switch to a different URL.

confirm dialog

1) How does it pass contact resolve?

As you stated, the contact resolve is available in the nested state. However, the editContact component doesn't have a contact binding. Instead, it has a pristineComponent binding.

In order to map the contact resolve to the pristineContact component binding, a bindings mapping is added to the state.

      bindings: { pristineContact: "contact" },

This supplies pristineContact component binding with the data from the contact resolve.

This guide describes resolve bindings: https://ui-router.github.io/guide/ng1/route-to-component#resolve-bindings

... and this API doc has even more of the gory details: https://ui-router.github.io/ng1/docs/latest/interfaces/ng1.ng1viewdeclaration.html#bindings

2) What is $ctrl.contact and where is pristineContact in the template?

The editContact component's dirty checking creates a copy of the original (pristine) contact in $onInit(). Any changes in the form are applied to the copy ($ctrl.contact):

  $onInit() {
    // Make an editable copy of the pristineContact
    this.contact = angular.copy(this.pristineContact);
  }

When the user clicks save, the editable copy is saved and then the parent state is activated, with reload: true to reload the changed data.

  /** Save the contact, then go to the grandparent state ('contacts') */
  save(contact) {
    this.Contacts.save(contact)
        .then(() => this.canExit = true)
        .then(() => this.$state.go("^", null, { reload: true }));
  }

The pristineContact is only used to make the initial copy and then to perform the dirty checking:

  uiCanExit() {
    if (this.canExit || angular.equals(this.contact, this.pristineContact)) {
      return true;
    }

    let message = 'You have unsaved changes to this contact.';
    let question = 'Navigate away and lose changes?';
    return this.DialogService.confirm(message, question);
  }

Upvotes: 1

Related Questions