Refactor Employee Form: Dynamic Logic By Profile

by Alex Johnson 49 views

This article details the refactoring of an employee registration form to dynamically adjust its structure based on the selected profile (Professor, Secretary, and Teaching Assistant). The current static form structure is inadequate for handling the diverse data requirements of each profile. The goal is to implement a dynamic FormGroup that adds or removes fields and validations programmatically using addControl and removeControl, ensuring that form.valid accurately reflects the visible and mandatory fields for the selected profile. Additionally, the submission process will be routed to distinct services with profile-specific payloads.

Technical Context

  • Component: src/app/pages/secretaria/funcionario-form/
  • New Service: src/app/services/auxiliar-docente/auxiliar-docente.service.ts
  • Key Concept: Dynamic manipulation of AbstractControl in ReactiveFormsModule.

Acceptance Criteria and Tasks

The refactoring process is structured into several key tasks, each with specific acceptance criteria to ensure the final solution meets the requirements.

1. 🔌 Creating the AuxiliarDocenteService

This initial step involves setting up the service layer for handling Teaching Assistant data. The service will manage the creation of new Teaching Assistant records. Let's walk through the details:

  • Service File Creation: The first sub-task involves creating a new service file, which serves as the central component for managing the business logic related to the Teaching Assistant profile. This file will house the methods responsible for interacting with the backend API, handling data serialization, and managing application state related to Teaching Assistants. Ensuring the service file is properly set up is crucial for maintaining a clean and modular architecture.
  • Model Creation: The next step is defining the data structure for the Teaching Assistant. This involves creating a TypeScript interface named AuxiliarDocente. This interface specifies the properties and their types that represent a Teaching Assistant's data, including id (number), nome (string), email (string), and area (string). Defining this model ensures type safety and consistency throughout the application, making the codebase easier to understand and maintain. This is where strong typing really shines, preventing common data-related errors.
  • Implementing the criar Method: This is a core part of the service implementation. The criar method is responsible for sending a request to the backend API to create a new Teaching Assistant record. It takes dados of type criarAuxiliarDocenteRequest as input and returns an Observable<AuxiliarDocente>. The method should use Angular's HttpClient to make a POST request to the /auxiliar-docentes endpoint. Properly implementing this method ensures that the application can successfully create new Teaching Assistant records in the system. Using Observables allows for asynchronous operations, which is essential for modern web applications.
export interface AuxiliarDocente {
  id: number,
  nome: string,
  email: string,
  area: string
}
// types/auxiliardocente.types.ts
export type criarAuxiliarDocenteRequest = Omit<AuxiliarDocente, 'id'>

2. 🧠 Form Logic (.ts)

Refactoring the form's TypeScript logic is crucial for implementing the dynamic behavior. This involves injecting the new service, refactoring the ngOnInit method, and implementing an observer for the profile selection. Here's a breakdown:

  • Service Injection: The first step is to inject the AuxiliarDocenteService into the component. This allows the component to use the service's methods, such as criar, to interact with the backend API. Dependency injection is a powerful feature of Angular that promotes modularity and testability. By injecting the service, the component can easily access the functionality needed to manage Teaching Assistant data.
  • Refactoring ngOnInit: The ngOnInit lifecycle hook is the perfect place to initialize the FormGroup with the common fields (nome, email, perfil). This ensures that the form is set up correctly when the component is initialized. Refactoring this method to handle the initial form setup is essential for the dynamic form logic to work correctly. By initializing the common fields first, the component can then dynamically add or remove fields based on the selected profile.
  • Implementing Profile Observer: This is where the magic happens. An observer needs to be set up to listen for changes to the 'perfil' form control. When the profile changes, the atualizarEstruturaFormulario method will be called. This dynamic behavior is the core of the refactoring effort. Listening for changes allows the form to adapt in real-time to the user's selections. This observer pattern is a powerful way to handle asynchronous events and keep the UI in sync with the application state.
  • Creating atualizarEstruturaFormulario: This private method is responsible for dynamically adding and removing form controls based on the selected profile. It's the heart of the dynamic form logic. Let's dive into its responsibilities:
    • If Auxiliar Docente: The method should remove the registro control, remove or clear the disciplinas control, and add the area control, making it required. This ensures that the form only includes the fields relevant to the Teaching Assistant profile.
    • If Professor: The method should remove the area control, add the registro control (making it required), and add the disciplinas control as an array. This configures the form for the Professor profile, which has different data requirements.
    • If Secretaria: The method should remove the area control, remove or clear the disciplinas control, and add the registro control (making it required). This sets up the form for the Secretary profile.
  • Calling updateValueAndValidity(): After making changes to the form controls, it's crucial to call updateValueAndValidity() on the FormGroup. This ensures that the form's validation status is updated to reflect the new form structure. Forgetting this step can lead to unexpected behavior and validation errors. This method triggers the validation process, ensuring that the form's valid property accurately reflects the current state.

3. 🎨 Template Adaptation (.html)

Adapting the HTML template is necessary to conditionally render form fields based on the selected profile. This ensures that only relevant fields are displayed to the user, improving the user experience.

  • Using @If: The key to conditional rendering in Angular templates is the @If directive. This directive allows you to show or hide elements based on a condition. In this case, the condition will be based on the selected profile value. Using @If directives ensures that the template remains clean and maintainable. This declarative approach to conditional rendering is a hallmark of Angular.
  • Checking Profile Value: Inside the @If directives, you can check the value of the profile form control. This allows you to render different sections of the form based on the selected profile. Checking the profile value dynamically adjusts the form's appearance and functionality. This dynamic behavior is essential for creating a user-friendly and efficient form.
  • Specific Field Rendering:
    • registro Input: This input should only be displayed for the Professor and Secretary profiles. Using @If directives, you can conditionally render this input based on the selected profile. Displaying the registro input only when needed keeps the form uncluttered and focused.
    • area Input: This input should only be displayed for the Teaching Assistant profile. Similarly, an @If directive can be used to control the visibility of this input. Conditionally rendering the area input ensures that the form remains relevant to the selected profile.
    • disciplinas Box: This section should only be displayed for the Professor profile. An @If directive can be used to manage the visibility of this entire section. Displaying the disciplinas section only for Professors helps to maintain a clear and focused user interface.

4. 🚀 Submission Logic (onSave)

Refactoring the submission logic is crucial for handling the different payloads required by each profile. This involves using a switch statement to route the submission to the appropriate service with the correct data.

  • Using a switch(perfil) Statement: A switch statement is an elegant way to handle different submission scenarios based on the selected profile. Each case in the switch statement corresponds to a specific profile, allowing you to tailor the submission logic accordingly. This approach keeps the onSave method organized and easy to understand. Using a switch statement is a best practice for handling multiple conditional scenarios.
  • Payload Assembly:
    • Professor: The payload should include { nome, email, registro, disciplinas }. This payload will be sent to the ProfessorService. Assembling the correct payload ensures that the backend receives the necessary data for creating a Professor record.
    • Secretaria: The payload should include { nome, email, matricula: registro }. Note the mapping of registro to matricula. This payload will be sent to the SecretariaService. Mapping registro to matricula ensures that the data is correctly formatted for the backend.
    • Aux. Docente: The payload should include { nome, email, area }. This payload will be sent to the AuxiliarDocenteService. This payload contains the specific fields required for a Teaching Assistant record.
  • Centralized Response Handling: To avoid code duplication, it's best to centralize the handling of success and error responses from the Observable. This can be done by creating a helper function or using RxJS operators like tap and catchError. Centralized response handling promotes code reusability and maintainability. This approach reduces the risk of errors and makes the codebase easier to manage.

Expected Payload Structure

To ensure that the frontend sends the correct data to the backend, it's crucial to understand the expected payload structure for each profile.

// Auxiliar Docente
{
  "nome": "João Silva",
  "email": "joao@fatec.sp.gov.br",
  "area": "Laboratório de Informática"
}

// Secretaria
{
  "nome": "Maria Souza",
  "email": "maria@fatec.sp.gov.br",
  "matricula": "123456" // Note o mapeamento de 'registro' para 'matricula'
}

Conclusion

Refactoring the employee registration form to dynamically adapt to different profiles enhances the application's flexibility and user experience. By implementing dynamic form controls and tailoring submission logic, the application becomes more robust and maintainable. This article provides a comprehensive guide to achieving this refactoring, ensuring that the form accurately captures the required data for each employee profile.

For further information on Reactive Forms in Angular, you can refer to the official Angular documentation: Angular Reactive Forms.