How to write concise, elegant and maintainable components?

Keywords: angular

Functional separation

This is an object-oriented idea. In components, many functions are independent, such as sending authentication codes, confirming passwords and so on. Writing these logic in a component encapsulates one or more functions has little effect when the component is small, but when the function of the component is more complex, there will be some problems:

  1. Component logic areas can become very large, and it is difficult to identify them at a glance when various methods are mixed up.
  2. Because the variables and methods needed to define the function are not together, the modification is troublesome.

Function separation is to extract these functions, write a class, and then introduce them into components.
The following is a simple class of pop-up window control functions and the use of this class:

export class DialogCtrl {
  isVisible = false;

  open () {
    this.isVisible = true;
  }
  close () {
    this.isVisible = false;
  }
}

Then introduce and instantiate in the required components:

DialogCtrl = new this.CommonService.DialogCtrl(); // Whether to open the bullet window or not

This can be used directly in html:

<nz-modal
    [nzVisible]="DialogCtrl.isVisible" 
    [nzTitle]="'Update password'" 
    [nzContent]="modalContent" 
    (nzOnCancel)="DialogCtrl.close()" 
    [nzConfirmLoading]="isSubmiting"
    nzOkText="Preservation"
    (nzOnOk)="savePassword()"></nz-modal>

This nz-modal is a bullet window, in the component we only have one variable declaration, so concise! In html, the form of DialogCtrl.isVisible and DialogCtrl.close() is also easy to understand its role and origin.
Another advantage of this is that it facilitates reuse. For functions that can be reused, such as the logic above that sends validation codes, a global service can be built to provide it. In angular, it is easy to implement through angular services and dependency injection. Here is my common.service.ts service file with centralized functions:


Paste_Image.png

common.servide.ts file:

@Injectable()
export class CommonService {
  // Functional class set
  public DialogCtrl = DialogCtrl;
  public MessageCodeCtrl = MessageCodeCtrl;
  public CheckPasswordCtrl = CheckPasswordCtrl;

  constructor(
    private http: HttpClient
  ) { }

  /* Get the SMS Verification Code (the method needed for these functions)
  -------------------------- */
  public getVerificationCode (phoneNum: string): Observable<any> {
    return this.http.get('/account/short_message?phoneNumber=' + phoneNum);
  }
}

Where needed, just inject this service to get the desired functionality. Compared with building a component directly, I think there are some advantages in writing this way.
Flexibility is higher. Writing as a component has style limitations, but not in this way.
More concise. Write as a component and communicate with it only through the way that the parent component passes in variables and monitors the events of the child component. Inevitably, the component you use will have more variables and methods.

State management

I don't know if you guys feel like this. When you write a new project, you feel clear logic, concise code, and functions have been implemented. But when you go to see or change your code for a while, wow, what's this? At least I have: flushed:
The complexity of the front end comes from countless states, so I set up a centrally managed state object for those components with complex states (here I call Impure):

/* Definition of variables -- state
  -------------------------- */
  registerForm: FormGroup;  // Registration Account Form
  registerInfoForm: FormGroup; // Corporate Information Form
  isSubmitting = false; // Is the form being submitted?
  nowForm = 'registerForm';  // Form currently in operation
  MessageCodeCtrl = new this.CommonService.MessageCodeCtrl(this.Msg, this.CommonService); // Verification code control

  /* Variable Definition--Fixed Value
  -------------------------- */
  registerFormSubmitAttr = ['login', 'password', 'shortMessageCode', 'roles', 'langKey'];
  registerInfoFormFormSubmitAttr = ['simName', 'contacter', 'officeTel', 'uid'];

  /* Change state events
  -------------------------- */
  Impure = {

    // Form initialization
    RegisterFormInit: () => this.registerForm = this.registerFormInit(),
    RegisterInfoFormInit: () => this.registerInfoForm = this.registerInfoFormInit(),

    // Verification code is not valid
    MessageCodeInvalid: {
      notSend: () => this.Msg.error('You haven't sent the authentication code yet.'),
      notRight: () => this.Msg.error('Verification code error')
    },

    // Form submission
    FormSubmit: {
      invalid: () => this.Msg.error('Wrong filling in the form'),
      before: () => this.isSubmitting = true,
      registerOk: () => {
        this.Msg.success('Account Registration Successful');
        this.nowForm = 'registerInfoForm';
      },
      registerInfoOk: () => {
        this.Msg.success('Successful Preservation of Information!Please wait patiently for the administrator's review');
        this.Router.navigate(['/login']);
      },
      fail: () => this.Msg.error('Submission failed, please try again'),
      after: () => this.isSubmitting = false
    }
  };

This is a simple registration component with two forms, because the two forms are highly coupled with html, so they are written together.
In the component, variables are divided into two categories: state and value. An Impure object is declared to centrally manage these states. In principle, all state changes in the component are written in Impure, while the judgment conditions of event triggering and data processing are written outside Impure.
You can compare the two forms submission methods using Impure and not using Impure:

/* Impure Account Form Submission
  -------------------------- */
  async register (form) {
    const _ = this.Fp._; // ramda library for data processing
    const { MessageCodeInvalid, FormSubmit } = this.Impure;

    // The form is illegal
    if (form.invalid) { FormSubmit.invalid(); return; }

    // Verification code is not valid
    if (!this.MessageCodeCtrl.code) { MessageCodeInvalid.notSend(); return; }
    if (this.MessageCodeCtrl.code !== form.controls.shortMessageCode.value) { MessageCodeInvalid.notRight(); return; }

    // Form submission
    FormSubmit.before();
    const data = _.compose(_.pick(this.registerFormSubmitAttr), _.map(_.prop('value')))(form.controls); // data processing
    const res = await this.AccountService.producerRegisterFirst(data).toPromise();
    if (!res) { FormSubmit.registerOk(); } else { FormSubmit.fail(); }
    FormSubmit.after();
  }

  /* Corporate Information Form Submission (non-Impure)
  -------------------------- */
  async registerInfo ({ simName, contacter, officeTel }) {
    // The form is illegal
    if (this.registerInfoForm.invalid) { this.Msg.error('Wrong filling in the form'); return; }

    // Form submission
    this.isSubmitting = true;
    const data = { // data processing
      simName: simName.value,
      contacter: contacter.value,
      officeTel: officeTel.value,
      uid: this.registerForm.controls.phone.value
    };
    const res = await this.AccountService.producerRegisterSecond(data).toPromise();
    if (!res) {
      this.Msg.success('Successful Preservation of Information!Please wait patiently for the administrator's review');
      this.Router.navigate(['/login']);
    } else {
      this.Msg.error('Submission failed, please try again');
    }
    this.isSubmitting = false;
  }

After using Impure to manage the state, the logic is clear. When submitting the form, you just need to pay attention to the condition of the event, while the second condition and state will be very confusing (actually, I wrote before), and you can't see when the state change happens at a glance, especially when you come back to see it for a while.
In fact, data processing here (data in this case) should be taken out to write a method separately, I just want to top up the advantages of using pure functions to process data, here is used for ramda library. Compared with the second processing method, the first one is more elegant and concise. It is easy to see what the source of data is (form. controls here). It is also very reusable to separate data processing functions.
If you want to change the successful status of the two tables one day, you don't need to find them in these two long submission functions and then change them one by one. Just change them in Impure, you don't even need to look at the two submission methods.
In this way, a component can be roughly divided into four parts: state, impure, event of changing state (condition of judging state change), and data processing (pure function). Each component has its own functions and is well maintained.

epilogue

These are suitable for me, but not necessarily for everyone. Everyone has his own style. It's good for you to feel them. In the future, I will share other aspects of the summary.

Students at the front of learning pay attention to it!!!
If you encounter any problems in the learning process or want to obtain learning resources, you are welcome to join the front-end learning exchange group 461593224. Let's learn the front-end together!

Posted by boon4376 on Wed, 22 May 2019 18:35:19 -0700