Adding Prompts - Part 3
Our prompt code is now almost complete. If you did any testing of your prompts after the last page you may have noticed that your page isn't refreshing quite right. This is due to the change detection onPush strategy we are using where we need to tell our page to refresh itself.
The default change detection in Angular constantly checks for changes and if we had been using it, our MPage would have properly refreshed our screen without the need to click on things to make it happen. This of course comes with a performance trade-off which depending on the complexity of the MPage can be noticeable to your users.
To fix our problem, we can make use of the Angular ChangeDetectorRef object and the DoCheck lifecycle hook.
-
Open appointment-data.service.ts and add a new public variable called refresh. It
can be directly below the "public loading = false;" line.
public refresh = false;
-
Scroll down to your callback in the customService.load method where you assign the loading variable to false.
Add the following highlighted code.
public loadAppointments(): void { this.loading = true; this.customService.load({ customScript: { script: [ { name: '1trn_appt_mp:group1', run: 'pre', id: 'appointments', parameters: JSON.stringify(this.prompts) } ] } }, undefined, (() => { this.loading = false; this.refresh = true; })); }
When our CCL script finishes executing it will assign a value of true to the refresh variable. -
Open appointment-data.service.ts and modify the code as shown below.
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, DoCheck } from '@angular/core'; import { AppointmentDataService } from '../appointment-data.service'; @Component({ selector: 'app-appointment-summary', templateUrl: './appointment-summary.component.html', styleUrls: ['./appointment-summary.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class AppointmentSummaryComponent implements OnInit, DoCheck { constructor(public appointmentDS: AppointmentDataService, public cdr: ChangeDetectorRef) { } ngOnInit(): void { } ngDoCheck(): void { if (this.appointmentDS.refresh === true) { setTimeout(() => { this.appointmentDS.refresh = false; }); this.cdr.detectChanges(); } } }
When implemented in a component, ngDoCheck() runs constantly just as the default Angular change detection does except it only executes the code you specify. In our code, we perform a check to see if the refresh value is set to true. If qualified we then use the change detector reference object (cdr) to issue the detectChanges() method for our component and all child components. This results in the change detector being fired for our component as soon as data has returned from CCL.
You will notice that setting the refresh value back to false is wrapped inside a setTimeout method. When we put this code inside the setTimeout method it gives our other components an opportunity to read the refresh value before it is changed.
-
Our toolbar.component.ts file also needs to perform change detection. To do this,
import the ChangeDetectorRef and DoCheck objects the same as we did in the appointment summary component. The
only difference is we don't need to set the refresh value to false as the appointment summary component takes
care of that after all of our change detection has completed.
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, DoCheck } from '@angular/core'; import { AppointmentDataService } from '../appointment-data.service'; @Component({ selector: 'app-toolbar', templateUrl: './toolbar.component.html', styleUrls: ['./toolbar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class ToolbarComponent implements OnInit, DoCheck { public dateTypes = [ { key: '30', value: '30 Days' }, { key: '60', value: '60 Days' }, { key: '120', value: '120 Days' }, { key: 'DATE', value: 'Date Range' }, { key: 'ALL', value: 'All Dates' } ]; constructor(public appointmentDS: AppointmentDataService, public cdr: ChangeDetectorRef) { } ngOnInit(): void { } ngDoCheck(): void { if (this.appointmentDS.refresh === true) { this.cdr.detectChanges(); } } }
You may have noticed that we have never used the ngOnInit() method in our application. This method is used when the component has first initialized and is a fantastic place for initializing settings on your component. It just happens that this MPage doesn't need to make use of ngOnInit().
I have kept ngOnInit() in each of our custom components however if you do not plan on using it in your components you can improve readability of your code by removing the ngOnInit() method, the OnInit in the implements statement and of course removal from the import statement.
This is no noticeable performance difference in keep empty methods in your code so the choice of removal is completely up to you.
Saving User Prompts
Saving the values entered by our users in their prompts is very simple and thankfully most of the code has already been developed when we wrote the code to save the table column preferences.
-
Start by modifying the savePreferences() method in appointment-data.service.ts. You
only need to add a prompts entry to the infoLongText object as highlighted below.
// Save user preferences public savePreferences(): void { this.customService.executeDmInfoAction('saveUserPrefs', 'w', [ { infoDomain: '1trn_appt_mp', infoName: 'column_prefs', infoDate: new Date(), infoChar: '', infoNumber: 0, infoLongText: JSON.stringify({ columnConfig: this.columnConfig, prompts: this.prompts }), infoDomainId: this.customService.mpage.prsnlId } ], () => { this.snackBar.open('Saved Preferences.', 'Ok', {duration: 1000}); }); }
-
The next step is to modify the loadPreferences() method to assign the prompts variable. Since we have already saved columnConfig values in our previous tests, we can't make an assumption that our saved data is going to have everything we need. Simply having the line "this.prompts = config.prompts" would throw an undefined error as our DM Info data only has columnConfig data saved in it.
Instead, we will use the ?? operator to assign our value if it is note undefined. If it is undefined we will keep the existing values. We are also going to change our columnConfig load to do the same thing.
As a final step, we are going to default the two date values to today's date as it doesn't make sense to have users save a date range.
// Load user preferences public loadPreferences(): void { this.loading = true; const prefMessage = this.customService.emptyDmInfo; prefMessage.infoDomain = '1trn_appt_mp'; prefMessage.infoName = 'column_prefs'; prefMessage.infoDomainId = this.customService.mpage.prsnlId this.customService.executeDmInfoAction('userPrefs', 'r', [ prefMessage ], () => { // Check for user preferences and assign them if (this.customService.isLoaded('userPrefs')) { const config = JSON.parse(this.customService.get('userPrefs').dmInfo[0].longText); this.columnConfig = config.columnConfig ?? this.columnConfig; this.prompts = config.prompts ?? this.prompts; // Default the dates to today this.prompts.fromDate = new Date(); this.prompts.toDate = new Date(); } this.loadAppointments(); }); }
- You can now test your MPage by saving some preferences and refreshing your browser. You should see your selections populate in the prompts as they are loaded.
Clearing Prompts
Our MPage is almost complete however one problem still exists. Now that our users can save their default prompt values, they have no easy way of clearing their defaults for new runs. Your users will not be happy if they have to un-select values from each prompt to perform a new run.
We are going to add a "Clear All" button to our prompts that will give our users the ability to reset their prompts to the system default. This will not clear the saved settings and on a page refresh the saved values will be restored unless of course the user hits the "Clear All" button and then the "Save" button.
-
Open appointment-data.service.ts and add the following two methods at the bottom of
the class.
// Empty Prompts public get emptyPrompts(): any { return ({ dateType: '30', fromDate: new Date(), toDate: new Date(), appointmentType: [], resource: [], location: [], schState: [] }); } // Clear prompt values public clearPrompts(): void { this.prompts = this.emptyPrompts; }
The emptyPrompts() get method returns the same content we initially stored in the prompts variable. We are assigning this value to the prompts variable when the clearPrompts() method is called. -
Scroll up to the top of appointment-data.service.ts and replace the prompts variable
definition with "this.emptyPrompts". This will eliminate the duplicate code definition allowing for easier
modifications later if you want to add more prompts to your MPage.
public prompts = this.emptyPrompts;
- If you view your MPage now you should see no difference in functionality.
-
Open toolbar.component.html and add a clear button with the following code directly
after the run button.
<!-- Clear Prompts --> <button mat-icon-button title="Clear Prompts" (click)="appointmentDS.clearPrompts()"> <mat-icon>clear_all</mat-icon> </button>
- View your MPage, the prompts should appear as follows after you click the clear button