Search code examples
angulartypescriptangular-components

ERROR TypeError: _co.saveXML is not a function


In my client Angular project I have a component called report-viewer. Inside it I have a form, EmailSettings, which is called when a button from component is pressed.

enter image description here

The form code is the following:

EmailSettings.html

<h1 mat-dialog-title>Email Settings</h1>
<div mat-dialog-content>
    <form id="formID" class="example-form">
        <mat-form-field *ngIf="data.smtpHost === ''" class="example-full-width" appearance="fill">
            <mat-label>SMTP Host</mat-label>
            <textarea matInput placeholder="Ex: smtp.gmail.com"></textarea>
        </mat-form-field> <br>
        <mat-form-field *ngIf="data.smtpPort === ''" class="example-full-width" appearance="fill">
            <mat-label>SMTP Port</mat-label>
            <textarea matInput placeholder="Ex: 587"></textarea>
        </mat-form-field> <br>
        <mat-form-field *ngIf="data.smtpUserName === ''" class="example-full-width" appearance="fill">
            <mat-label>SMTP User Name</mat-label>
            <textarea matInput placeholder="Ex: [email protected]"></textarea>
        </mat-form-field> <br>
        <mat-form-field *ngIf="data.smtpUserPassword === ''" class="example-full-width" appearance="fill">
            <mat-label>SMTP User Password</mat-label>
            <textarea matInput placeholder="Ex: password"></textarea>
        </mat-form-field> <br>
        <mat-form-field *ngIf="data.smtpFrom === ''" class="example-full-width" appearance="fill">
            <mat-label>SMTP From</mat-label>
            <textarea matInput placeholder="Ex: [email protected]"></textarea>
        </mat-form-field> <br>
        <mat-form-field *ngIf="data.smtpDisplayName === ''" class="example-full-width" appearance="fill">
            <mat-label>SMTP Display Name</mat-label>
            <textarea matInput placeholder="Ex: Jhon"></textarea>
        </mat-form-field> <br>
        <button mat-button (click)="saveXML()">Save</button>
     <!--    <mat-form-field class="example-full-width" appearance="fill">
            <button mat-button (click)="saveXML()">Save</button>
        </mat-form-field> -->
    </form>
</div>

EmailSettings.ts

import { Component, OnInit, ViewChild, ViewEncapsulation, ChangeDetectorRef, Inject } from '@angular/core';
import * as FileSaver from 'file-saver';
import { MatInputModule } from '@angular/material/input';

@Component({
    selector: 'app-EmailSettings',
    encapsulation: ViewEncapsulation.None,
    templateUrl: './EmailSettings.html',
  
  })

  export class EmailSettings{
    appIdOpts: any = [];
    saveXML(){
        console.log("TestXML");
        let blob = new Blob([document.getElementById('formID').innerHTML], {type: "text/xml"});
        FileSaver.saveAs(blob, "blobExport.xml");
      }

  }

When I press on form's Save button I get the error ERROR TypeError: _co.saveXML is not a function.

enter image description here

I know that there is already a post about it, but didn't help me solve the error. What could I do?


EDIT:

As @SirOneOfMany suggested, I have created a new component, EmailSettings. My new structure is at follows:

enter image description here

As you have requested, my report-viewer.component.ts has the following content. Note that it contains some other functions which have no relevance to this topic. The function is relevant for us is called openDialog().

import { Component, OnInit, ViewChild, ViewEncapsulation, ChangeDetectorRef, Inject } from '@angular/core';
import 'devexpress-reporting/dx-richedit';
import { DxReportViewerComponent } from 'devexpress-reporting-angular';
import { HttpClient } from '@angular/common/http';
import DevExpress from '@devexpress/analytics-core';
import List from "devextreme/ui/list";
import { DxSelectBoxModule, DxCheckBoxModule, DxListModule } from 'devextreme-angular';
import { ParagraphPropertiesKeepLinesTogetherDescriptor } from 'devexpress-richedit/lib/core/model/paragraph/paragraph-property-descriptors';
import { DxDropDownBoxModule, DxTreeViewModule, DxDataGridModule, DxTreeViewComponent, } from 'devextreme-angular';
import DXAnalytics from '@devexpress/analytics-core/dx-analytics-core'
import * as ko from 'knockout';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material';
import { MatInputModule } from '@angular/material/input';
import { EmailSettingsComponent } from '../email-settings/email-settings.component';  



@Component({
  selector: 'app-report-viewer',
  encapsulation: ViewEncapsulation.None,
  templateUrl: './report-viewer.component.html',
  styleUrls: [
    "./report-viewer.component.css",
    "../../../node_modules/jquery-ui/themes/base/all.css",
    "../../../node_modules/devextreme/dist/css/dx.common.css",
    "../../../node_modules/devextreme/dist/css/dx.light.css",
    "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.common.css",
    "../../../node_modules/@devexpress/analytics-core/dist/css/dx-analytics.light.css",
    "../../../node_modules/devexpress-reporting/dist/css/dx-webdocumentviewer.css"
  ]

})



export class ReportViewerComponent implements OnInit {


  @ViewChild(DxReportViewerComponent, { static: true }) viewer: DxReportViewerComponent;
  @ViewChild('selectedReport', { static: false }) public selectedReport: "C:\\ReportDesigner\\Reports";
  @ViewChild("emailSettings", { static: true }) emailSettings: EmailSettingsComponent;

 // @ViewChild('selectedReport', { static: false }) public selectedReport: "@/Reports";

  treeBoxValue: 'selectedReport';
  isTreeBoxOpened: boolean;
  

  title = 'DXReportViewerSample';
  hostUrl = 'http://localhost:54111/';
  invokeAction: string = "/WebDocumentViewer/Invoke";
  reportUrl: string = 'Employee'; 

//some other functions here...

  ngOnInit() {
      var ajaxSetup = DXAnalytics.Analytics.Utils.ajaxSetup
    ajaxSetup.ajaxSettings = {
      xhrFields: {
        withCredentials: true
     }
  };
  }

  openDialog(){ //this functions is used to open the form which was on EmailSettings.html)
    this.dialog.open(EmailSettingsComponent, {
      data: {
       // animal: 'panda',
        smtpHost: '',
        smtpPort: '',
        smtpUserName: '',
        smtpUserPassword: '',
        smtpFrom: '',
        smtpDisplayName: '',
      },
    });
  }
}

export interface DialogData { 
  smtpHost: any;
  smtpPort: any;
  smtpUserName: any;
  smtpUserPassword: any;
  smtpFrom: any;
  smtpDisplayName: any;
}

@Component({
  selector: 'app-email-settings',
  templateUrl: 'email-settings',
})
export class EmailSettings {
  constructor(@Inject(MAT_DIALOG_DATA) public data: DialogData) {}
} 

At this point I get the error ERROR in ./src/app/report-viewer/report-viewer.component.ts Module not found: Error: Can't resolve './email-settings' in 'C:\ProiectVisualStudio\ProjectName\JS\angular-report-designer\src\app\report-viewer'


Solution

  • As I already stated in the comments section you will have to declare a component. Just adding a decorator is not enough. Every class you add @Component(...) to needs to be added to the declarations array of a module.

    I assume you just have one single AppModule class. So you will need to add your component to this module.

    You used

    @Component({
        selector: 'app-EmailSettings',
        encapsulation: ViewEncapsulation.None,
        templateUrl: './EmailSettings.html',
    })
    

    So you will need to add your EmailSettings class to your modules declarations array:

    @NgModule({
      imports:      [ BrowserModule ],
      declarations: [ AppComponent, ReportViewerComponent, EmailSettings ],
      bootstrap:    [ AppComponent ]
    })
    export class AppModule { }
    

    That is why I said you should create your components with ng schematics, because angular already does this for you.

    So just use:

    ng generate component relative/path/in/src/folder/email-settings
    

    This command will create the files for your component (.ts, .html, .(s)css, .spec.ts) and add the component to the closest module.

    Have a look at this article for a more precise explanation about modules


    EDIT

    Thanks for editing your question. At the very end of your report-viewer component you have the following that might throw an error

    @Component({
      selector: 'app-email-settings',
      templateUrl: 'email-settings', // this should throw the error
    })
    export class EmailSettings {
      constructor(@Inject(MAT_DIALOG_DATA) public data: DialogData) {}
    }
    

    Remove that class and use your newly generated component. And as a small best practice hint:

    Just implement only one class decorated via @Component per file

    Use ng schematics to generate code, that way you can ensure your stuff is consistent through your whole app.

    Give each interface a single file and move it to a models folder