SBB
SBB

Reputation: 8990

Angular component firing more than expected?

I have a component that I am working with that contains a table of records. Each row in this table has an edit button that allows the content to be edited via modal.

I threw a console.log in my function that renders the content of my modal and I am noticing some odd behavior. Each time I open an close the modal, my console.log seems to fire an additional time with each click. For example, on load, I click Edit. Results in console.log('test');. If I close the modal and click edit again (with the console cleared), I get two instances of console.log('test');.

I am a little skeptical that something is not working correctly and being fired more than it should.

Results Table Component:

<!-- Loading Indicator -->
<div *ngIf="!exceptions" class="loader" align="center">
    <img src="images/loading-bars.svg" alt="" />
</div>
<!-- Loading Indicator -->

<!-- Active Exceptions -->
<span *ngIf="exceptions">
    <table class="table table-condensed" *ngIf="exceptions.activeExceptions">
        <thead>
            <tr>
                <th class="small">Employee</th>
                <th class="small">Start Date</th>
                <th class="small">End Date</th>
                <th class="small">Exception Outcome</th>
                <th class="small">Action</th>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let e of exceptions.activeExceptions.data">
                <td [innerHtml]="e.EmpNTID | ntidLink"></td>
                <td class="small">{{ e.ExceptionStartDate }}</td>
                <td class="small">{{ e.ExceptionEndDate === '2050-12-31' ? 'Indefinitely' : e.ExceptionEndDate }}</td>
                <td class="small"><strong>{{ e.Value }}</strong></td>
                <td class="small">
                    <button type="button" class="btn btn-default btn-xs" (click)="doEditException(e)">
                    <i class="fa fa-pencil"></i>&nbsp;&nbsp;Edit Exception
                    </button>
                </td>
            </tr>
        </tbody>
    </table>
    <!-- Active Exceptions -->

    <!-- No Active Exceptions -->
    <span *ngIf="!exceptions.activeExceptions">
        <div class="alert alert-warning"><i class="fa fa-comment padRight"></i>No Active Exceptions</div>
    </span>
    <!-- No Active Exceptions -->

</span>

Component Associated with this Table Data:

export class ActiveExceptionsComponent implements OnInit {

    @Input() exceptions: any;
    @Output() doEdit:EventEmitter<any> = new EventEmitter();

    constructor(
        public bsModalRef: BsModalRef,
        private modalService: BsModalService,
        private _mapsService: MapsService,
    ) {
    }

    ngOnInit() {
        //
    }

    /**
     * On "Edit" of an exception, trigger our modal.
     * The modal will include its own component.
     * 
     * @param {any} $event 
     * @memberof ExceptionsComponent
     */
    doEditException($event) {

        // Call our modal, pass the EditExceptionModalComponent
        this.bsModalRef = this.modalService.show(EditExceptionModalComponent);

        // Call next on our behavior subject to update the modal subscriber data
        this._mapsService.updateExceptionModalData({
            event: $event,
            exceptionTypes: this.exceptions.exceptionTypes
        });

    }

The edit button click is bound to the doEditException method and loads the content of a modal with another component.

Modal Component:

export class EditExceptionModalComponent implements OnInit {

    exceptionForm: FormGroup;
    modalData: any;

    // Configuration for the date pickers
    datesConfig = {
        'format': 'YYYY-MM-DD',
        'placeholder': 'Choose a date...'
    }

    constructor(
        private fb: FormBuilder,
        public bsModalRef: BsModalRef,
        private _mapsService: MapsService,
    ) { }

    ngOnInit() {

        // Subscribe to the modal data received from the active/future exceptions component
        this._mapsService.uiExceptionModalData.subscribe(
            results => {
                if (results) {
                    this.modalData = results;
                    this.renderForm();
                }
            }
        );

    }

    /**
     * Render the form for the contents of the modal
     * 
     * @memberof EditExceptionModalComponent
     */
    renderForm() {

        console.log('here'); <---- Each time the parent component edit button is clicked, this method gets executed 1 more time than previous.

        // Determine if this exception expires or not
        let isIndefinite = (this.modalData.event.ExceptionEndDate == '2050-12-31' ? true : false);

        // Render our modal form
        this.exceptionForm = this.fb.group({
            outcome: [[{ text: this.modalData.event.Value, id: this.modalData.event.MappedValue }]],
            startDate: this.modalData.event.ExceptionStartDate,
            endDate: [{ value: (!isIndefinite ? this.modalData.event.ExceptionEndDate : ''), disabled: (isIndefinite  ? true : false) }],
            indefinite: (isIndefinite ? '1' : ''),
            exceptionUser: this.modalData.event.QID,
            exceptionID: this.modalData.event.ExceptionID,
            targetID: this.modalData.event.TargetID,
        });

    }

}

And finally, the HTML for the modal content:

<div *ngIf="modalData">
    <div class="modal-header text-center">
        <h4 class="modal-title">Edit Exception</h4>
        <small>Editing rule exception for <strong>{{ modalData.event.EmpName }}</strong></small>
    </div>
    <div class="modal-body">
        <form [formGroup]="exceptionForm" #f="ngForm">
            <div class="form-group">
                <label for="exceptionOutcome">Exception Outcome</label>
                <ng-select formControlName="outcome" [allowClear]="false" [items]="getExceptionTypes()" placeholder="Select an Exception Outcome">
                </ng-select>
            </div>
            <div class="form-group noBottomMargin">
                <label for="exceptionStartDate">Start Date</label>
                <input type="text" class="form-control input-sm" formControlName="startDate" [dpDayPicker]="datesConfig" theme="dp-material"
                    placeholder="Choose a Start Date">
            </div>
            <div class="form-group noBottomMargin">
                <label for="exceptionEndDate">End Date</label>
                <div class="input-group">
                    <input type="text" class="form-control input-sm" formControlName="endDate" [dpDayPicker]="datesConfig" theme="dp-material"
                        placeholder="Choose a End Date">
                    <div class="input-group-addon"><input type="checkbox" (click)="toggleIndefiniteCheckbox(true)" value="1" formControlName="indefinite" [value]="value" [attr.checked]="value" id="indefinitely"> Indefinitely</div>
                </div>
            </div>
            <input type="hidden" formControlName="exceptionID">
            <input type="hidden" formControlName="targetID">
        </form>
    </div>
    <div class="modal-footer">
        <button type="button" class="btn btn-default" (click)="bsModalRef.hide()">Close</button>
        <button type="button" class="btn btn-primary" (click)="saveChanges(exceptionForm.getRawValue())">Save changes</button>
    </div>
</div>

My concern here is that the renderForm method is being called multiple times per loading of the modal, increasing by one each time I fire the doEditException method in the parent.

Am I causing this behavior by not ending or resetting something correctly? My expectation is that if I were to click on the "Edit" button in my parent component, I would only ever see that the renderForm method in the child is only called one time. This doesn't appear to be the case with my console.log within that method increasing each time.

Upvotes: 1

Views: 294

Answers (1)

FAISAL
FAISAL

Reputation: 34691

The problem is that you are not unsubscribing the results subscription. You need to unsubscribe when your modal is destroyed.

In your Modal Component:

import { Subscription } from 'rxjs/Subscription';
// ....

private sub: Subscription;
ngOnInit() { 
    // Subscribe to the modal data received from the active/future exceptions component
    this.sub = this._mapsService.uiExceptionModalData.subscribe(
        results => {
            if (results) {
                this.modalData = results;
                this.renderForm();
            }
        });
}

ngOnDestroy() {
    // Unsubscribe here.
    this.sub.unsubscribe();
}

Upvotes: 1

Related Questions