3

I have modal on which I am searching location using google map services. I am getting error of TypeError: Cannot read properties of undefined (reading 'nativeElement')

HTML

<input
                type="text"
                [ngClass]="{ error_border: submitted && f.location.errors }"
                class="form-control"
                formControlName="location"
                [readonly]="viewMode"
                (keydown.enter)="$event.preventDefault()" 
                placeholder="Search Location" 
                autocorrect="off" 
                autocapitalize="off" 
                spellcheck="off" 
                #search
              />

TS

    export class ListComponent implements OnInit, AfterViewInit {
        
            dbLocation:any;
              latitude!: number;
              longitude!: number;
              zoom!: number;
              address!: string;
              private geoCoder:any;
              searchFlag:boolean = false;
              @ViewChild('search' , { static: true }) public searchElementRef!: ElementRef ;
            ngOnInit(): void {
            }
              ngAfterViewInit() {
//----------autocomplete code block------------
                 //load Places Autocomplete
                 this.mapsAPILoader.load().then(() => {
                  // this.setCurrentLocation();
                   this.geoCoder = new google.maps.Geocoder;
             
                   let autocomplete = new google.maps.places.Autocomplete(this.searchElementRef.nativeElement);
                   autocomplete.addListener("place_changed", () => {
                     this.ngZone.run(() => {
                       //get the place result
                       let place: google.maps.places.PlaceResult = autocomplete.getPlace();
             
                       //verify result
                       if (place.geometry === undefined || place.geometry === null) {
                         return;
                       }
             
                       //set latitude, longitude and zoom
                       this.latitude = place.geometry.location.lat();
                       this.longitude = place.geometry.location.lng();
                       this.getAddress(this.latitude, this.longitude);
                       this.zoom = 15;
                       this.searchFlag = true;
                     });
                   });
                 });
//----------autocomplete code block------------
                }
    }

I have tried adding { static: true } to viewChild. Also moving autocomplete code block from ngOnInit to ngAfterViewInit. Also adding timeout for autocomplete code block. But still giving that error.

It does not give error if input field is not on modal and autocomplete code block in ngOnInit. But I have form on modal in which I have that input field of location. There it is giving error.

I am not able to figure it out which combination of code will work. How can I resolve that error?

Please help and guide.

Edit modal HTML

<ng-template #formModal let-modal>
  <div class="modal-header">
    <h5 class="modal-title" id="modal-basic-title">
      Form
    </h5>
    <button
      type="button"
      class="close"
      aria-label="Close"
      (click)="modal.dismiss('Cross click')"
    >
      <span aria-hidden="true">
        <i class="fas fa-times"></i>
      </span>
    </button>
  </div>

  <div class="modal-body">
    <form [formGroup]="form">
      <div class="row edit_profile">
        <div class="col-sm-12">
          <div class="form-group">
            <label>Name<b style="color: red" *ngIf="!viewMode">*</b></label>
            <input
              type="text"
              [ngClass]="{ error_border: submitted && f.headline.errors }"
              formControlName="headline"
              class="form-control"
              [readonly]="viewMode"
            />
            <div *ngIf="submitted && f.headline.errors" class="text-danger">
              <div *ngIf="f.headline.errors.required">
                name is required
              </div>
            </div>
          </div>
        </div>
     

        <div class="col-sm-7">
          <div class="form-group">
            <label for="location">Location <b style="color: red" *ngIf="!viewMode">*</b></label>
            <div class="custome_input icons date_icon">
              <i class="fas fa-map-marker-alt"></i>
              <input
                type="text"
                [ngClass]="{ error_border: submitted && f.location.errors }"
                class="form-control"
                formControlName="location"
                [readonly]="viewMode"
                (keydown.enter)="$event.preventDefault()" 
                placeholder="Search Location" 
                autocorrect="off" 
                autocapitalize="off" 
                spellcheck="off" 
                #search
              />
            </div>
            <div *ngIf="submitted && f.location.errors" class="text-danger">
              <div *ngIf="f.location.errors.required">Location is required</div>
            </div>
          </div>
        </div>

      <div class="form-group btn-group mb-0" *ngIf="!viewMode">
        <button
          [disabled]="loading"
          class="btn"
          (click)="onSubmitForm()"
        >
          <span
            *ngIf="loading"
            class="spinner-border spinner-border-sm mr-1"
          ></span>
          Save
        </button>
      </div>
    </form>
  </div>
</ng-template>

TS of opening modal

 open(content) {
        
        this.listModalRef = this.modalService.open(content,{ size: 'lg', backdrop: 'static' });
        this.listModalRef.result.then((result) => {
          this.closeResult = `Closed with: ${result}`;
        }, (reason) => {
          this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
        });
        setTimeout(() => {
         //load Places Autocomplete
         this.mapsAPILoader.load().then(() => {
          // this.setCurrentLocation();
           this.geoCoder = new google.maps.Geocoder;
     
           let autocomplete = new google.maps.places.Autocomplete(this.searchElementRef.nativeElement);
           autocomplete.addListener("place_changed", () => {
             this.ngZone.run(() => {
               //get the place result
               let place: google.maps.places.PlaceResult = autocomplete.getPlace();
     
               //verify result
               if (place.geometry === undefined || place.geometry === null) {
                 return;
               }
     
               //set latitude, longitude and zoom
               this.latitude = place.geometry.location.lat();
               this.longitude = place.geometry.location.lng();
               this.getAddress(this.latitude, this.longitude);
               this.zoom = 15;
               this.searchFlag = true;
             });
           });
         });
        }, 0)
      }
ganesh
  • 416
  • 1
  • 11
  • 32

2 Answers2

2

put your code located inside your modal -> in a new component.

In this new component you can use @ViewChild to get an element value or native html element. Access the native element in ngAfterViewInit() (of your new component). It won't be undefined.

If you want to communicate with the parent component (component which holds the modal), use @Input/@Output, or use the ngrxStore (dispatch actions).

0

First, you should remove { static: true }, else searchElementRef will always be undefined.

Next change you would need to do, is to move this.mapsAPILoader.load().then(() => { ... } logic from ngAfterViewInit to a method wherein you are sure that the modal has been initialized. If you are using any library component (for modal) and if it provides an event (maybe onShown or show or whatever) to identify modal initialization, then you can put your logic there.

In short, you need to ensure that the modal is initialized before this.mapsAPILoader.load() promise is resolved, else you will always get searchElementRef as undefined.

If the above doesn't work, then you would need to create a separate component for your modal, instead of using ng-template. You can refer the comment within this issue

Siddhant
  • 2,911
  • 2
  • 8
  • 14
  • I have tried your solution. Added autocomplete code block in `open` method(which opens modal) after `this.modalService.open(content,{ size: 'lg', backdrop: 'static' });` code still its showing same error :( – ganesh Jan 04 '22 at 14:10
  • Removed static `true` too? If the Google maps is already loaded, the `load` will resolve immediately. Can you once wrap your autocomplete code block written after `modalService.open` within `setTimeout` and test. Also are you using ngx-bootstrap modal? – Siddhant Jan 04 '22 at 14:20
  • still not working after setting `setTimeout1` . added `TS of opening modal` in question. I am not using ngx-bootstrap modal , using NgbModal – ganesh Jan 04 '22 at 15:06
  • @ganesh Can you please share reproducible issue in [stackblitz](https://stackblitz.com/edit/angular-ivy-2jgvxf) ? I hope you have removed `{ static: true }` too – Siddhant Jan 04 '22 at 15:14
  • @ganesh The content of the `ng-template` after rendering within modal won't be part of the ListComponent View and hence the reference may not be available. Try creating a separate component for Modal. You can also refer [this](https://stackoverflow.com/questions/56301023/angular-viewchildren-not-working-when-elements-are-inside-a-modal) – Siddhant Jan 04 '22 at 16:10