vaadin / vaadin-grid

vaadin-grid is a free, high quality data grid / data table Web Component. Part of the Vaadin components.
https://vaadin.com/components
Apache License 2.0
401 stars 155 forks source link

Angular2: Cannot read property 'nativeElement' of undefined #411

Closed Splaktar closed 7 years ago

Splaktar commented 8 years ago

This was working fine with 1.1.0-beta4 using (grid-ready)="onGridReady($event)" event. Now that I've moved to 1.1.0 final, I can't seem to get the grid in a good way. It seems like the grid-ready event was removed?

So I've been trying to use the @ViewChild('grid') approach with ngAfterViewInit(), but that isn't working. I've tried ngAfterContentInit() as well w/ the same result.

Using: "vaadin-grid": "1.1.0", "@vaadin/angular2-polymer": "1.0.0-beta2", "@angular/common": "2.0.0-rc.4", "@angular/compiler": "2.0.0-rc.4", "@angular/core": "2.0.0-rc.4", "@angular/forms": "0.2.0", "@angular/http": "2.0.0-rc.4", "@angular/platform-browser": "2.0.0-rc.4", "@angular/platform-browser-dynamic": "2.0.0-rc.4", "@angular/platform-server": "2.0.0-rc.4", "@angular/router": "3.0.0-beta.2",

Template:

    <vaadin-grid #grid *ngIf="propertyCount > 0" visible-rows="15" class="flex-8"
                 (selected-items-changed)="onPropertySelected($event)">

Component:

...
export class AreasComponent implements OnInit, AfterViewInit {
  @ViewChild('grid') grid: ElementRef;
  vaadinGrid: any;
...
  ngAfterViewInit() {
    this.grid.nativeElement.then(grid => {
      this.vaadinGrid = grid;
    });
  }

Always results in:

EXCEPTION: Error in http://localhost:4200/app/+areas/areas.component.js class AreasComponent_Host - inline template:0:0
browser_adapter.ts:82
EXCEPTION: Error in http://localhost:4200/app/+areas/areas.component.js class AreasComponent_Host - inline template:0:0BrowserDomAdapter.logError @ browser_adapter.ts:82BrowserDomAdapter.logGroup @ browser_adapter.ts:93ExceptionHandler.call @ exception_handler.ts:58(anonymous function) @ application_ref.ts:374schedulerFn @ async.ts:148SafeSubscriber.__tryOrUnsub @ Subscriber.ts:240SafeSubscriber.next @ Subscriber.ts:192Subscriber._next @ Subscriber.ts:133Subscriber.next @ Subscriber.ts:93Subject._finalNext @ Subject.ts:154Subject._next @ Subject.ts:144Subject.next @ Subject.ts:90EventEmitter.emit @ async.ts:133onHandleError @ ng_zone_impl.ts:95ZoneDelegate.handleError @ zone.js:327Zone.runTask @ zone.js:259drainMicroTaskQueue @ zone.js:474ZoneTask.invoke @ zone.js:426
browser_adapter.ts:82
ORIGINAL EXCEPTION: TypeError: Cannot read property 'nativeElement' of undefinedBrowserDomAdapter.logError @ browser_adapter.ts:82ExceptionHandler.call @ exception_handler.ts:70(anonymous function) @ application_ref.ts:374schedulerFn @ async.ts:148SafeSubscriber.__tryOrUnsub @ Subscriber.ts:240SafeSubscriber.next @ Subscriber.ts:192Subscriber._next @ Subscriber.ts:133Subscriber.next @ Subscriber.ts:93Subject._finalNext @ Subject.ts:154Subject._next @ Subject.ts:144Subject.next @ Subject.ts:90EventEmitter.emit @ async.ts:133onHandleError @ ng_zone_impl.ts:95ZoneDelegate.handleError @ zone.js:327Zone.runTask @ zone.js:259drainMicroTaskQueue @ zone.js:474ZoneTask.invoke @ zone.js:426
browser_adapter.ts:82ORIGINAL STACKTRACE:BrowserDomAdapter.logError @ browser_adapter.ts:82ExceptionHandler.call @ exception_handler.ts:74(anonymous function) @ application_ref.ts:374schedulerFn @ async.ts:148SafeSubscriber.__tryOrUnsub @ Subscriber.ts:240SafeSubscriber.next @ Subscriber.ts:192Subscriber._next @ Subscriber.ts:133Subscriber.next @ Subscriber.ts:93Subject._finalNext @ Subject.ts:154Subject._next @ Subject.ts:144Subject.next @ Subject.ts:90EventEmitter.emit @ async.ts:133onHandleError @ ng_zone_impl.ts:95ZoneDelegate.handleError @ zone.js:327Zone.runTask @ zone.js:259drainMicroTaskQueue @ zone.js:474ZoneTask.invoke @ zone.js:426
browser_adapter.ts:82
TypeError: Cannot read property 'nativeElement' of undefined
    at AreasComponent.ngAfterViewInit (http://localhost:4200/app/+areas/areas.component.js:43:18)
    at _View_AreasComponent_Host0.detectChangesInternal (AreasComponent.template.js:40:65)
    at _View_AreasComponent_Host0.AppView.detectChanges (http://localhost:4200/vendor/@angular/core/src/linker/view.js:243:14)
    at _View_AreasComponent_Host0.DebugAppView.detectChanges (http://localhost:4200/vendor/@angular/core/src/linker/view.js:348:44)
    at _View_AppComponent4.AppView.detectContentChildrenChanges (http://localhost:4200/vendor/@angular/core/src/linker/view.js:261:19)
    at _View_AppComponent4.AppView.detectChangesInternal (http://localhost:4200/vendor/@angular/core/src/linker/view.js:253:14)
    at _View_AppComponent4.AppView.detectChanges (http://localhost:4200/vendor/@angular/core/src/linker/view.js:243:14)
    at _View_AppComponent4.DebugAppView.detectChanges (http://localhost:4200/vendor/@angular/core/src/linker/view.js:348:44)
    at _View_AppComponent0.AppView.detectContentChildrenChanges (http://localhost:4200/vendor/@angular/core/src/linker/view.js:261:19)
    at _View_AppComponent0.detectChangesInternal (AppComponent.template.js:207:8)
Splaktar commented 8 years ago

I've traced the problem down to this one line:

<vaadin-grid #grid *ngIf="propertyCount > 0"

Using #grid and *ngIf in the same element does not work in Angular 2-rc.4. Removing the *ngIf results in a valid element every time, where having it there always returns undefined.

Moving the *ngIf up to a parent div also breaks things. I guess that I'll have to resort to display: none via classes (normally something that seems to be avoided in Angular 2).

This worked fine when using the grid-ready events :( But it would be nice to use 'the Angular way here', unfortunately that way is either bugged or just doesn't allow an obvious use case.

Splaktar commented 8 years ago

Related: https://github.com/angular/angular/issues/6179 https://github.com/vaadin/vaadin-grid/issues/356 https://github.com/angular/angular/issues/4452 https://github.com/angular/angular/issues/6747

Splaktar commented 8 years ago

Investigated this some more via input from the Angular 2 team on https://github.com/angular/angular/issues/6179 and using this Plunkr. It seems to work fine with Angular 2 components, but not with angular2-polymer or vaadin-grid elements.

Perhaps angular2-polymer is not handling resolution of the components in the view before ngAfterViewInit()?

Saulis commented 8 years ago

angular2-polymer doesn't affect the resolution of view childs in any way – if childs are defined as a nested childs of a ngIf element, ngAfterViewInit() is run regardless if those childs are rendered in the DOM or not.

This issue affects all elements, as you can see with this snippet:

<input type="checkbox" [checked]="visible" (change)="visible=$event.target.checked">

<div *ngIf="visible">
  <label #hiddenLabel>Visible!</label>
</div>
export class AppComponent {
  @ViewChild('hiddenLabel') label: ElementRef;

  private visible: Boolean;

  constructor() {
    this.visible = false;
  }

  ngAfterViewInit() {
    console.log(this.label.nativeElement); // throws an error
  }
}

Your Plunkr example works in this case because things.length > 0 is true when ngAfterViewInit() is run.

Indeed grid-ready was handy in this case, since it could be used to detect when the element is actually rendered, but unfortunately it's gone now.

I'd suggest either using hidden instead of *ngIf or using such a trigger for *ngIf visibility that you can set the vaadinGrid variable simultaneously in that trigger to make sure #grid exists in the DOM.

jouni commented 7 years ago

As we are no longer actively supporting the use of our elements with Angular, I’m closing this issue. Please open a new issue in the angular-polymer project if you are still affected by this issue.

DuaneQ commented 6 years ago

I'm getting "Cannot read property 'nativeElement' of undefined" but only after I save to a firebase backend.

<p> <input [(ngModel)]="location" placeholder="Enter city" #search> </p>

`export class HomePage implements OnInit{ loggedInUser: any = null;

@ViewChild('search') public searchElement: ElementRef; @ViewChild('searchPlaces') public searchPlacesElement: ElementRef; @ViewChild('myInput') public myInput: ElementRef;`

` ngOnInit(){ this.mapsApiLoader.load().then( () => { let autocomplete = new google.maps.places.Autocomplete(this.searchElement.nativeElement, {types:['(cities)']});

      autocomplete.addListener("place_changed", () => {
        this.ngZone.run(() => {
            let place: google.maps.places.PlaceResult = autocomplete.getPlace();

            if(place.geometry === undefined || place.geometry === null){
              return;
          }
            this.location = place.formatted_address;
          });
        });
      }
    )`

` savePersonalInfo() { this.firebaseSvcProvider.addSettings(this.location, this.intPlaces, this.bio, this.connectionNotifications, this.messageNotifications, this.showTrips).then(() => { this.navCtrl.setRoot(PopularPage)});

        console.log(this.searchElement.nativeElement + ' save');
}`

After I save the data to firebase, when I navigate back to the page with the autofill I get the above mentioned error. I have to close the app then re-open in order for it to resume working.

senturkhasan commented 6 years ago

1294

toughthinktank commented 6 years ago

Using openModal type of method where modal has ngIf condition will set the native element to undefined as the native element is only created when the condition mentioned with ngIf becomes true. But there is a way around this if you absolutely have to use *ngIf by using Javascript's event loop manipulation using setTimeout or Promises or any sort of callbacks. Here's an example

HTML Code

<button (click)="openModal()">Open Modal</button>

<div class="modal fade" #sampleModal *ngIf="showModal">
       <!--Modal View goes here-->
</div>

TS Code

//Inside export component class
showModal:boolean = false;

openModal = function () {
        this.showModal = true;
        var that = this;
        setTimeout(() => {that.showHideModal('sampleModal', 'show')  }, 0)
}

showHideModal(id, showOrHide) {
        $(this[id].nativeElement).modal(showOrHide);
}

Now you won't get the 'nativeElement undefined error' even with ngIf. Why does this work? It is because of the angular/JS event loop. Basically ngIf ensures that the modal with the id as 'sampleModal' only comes into the DOM as a native element when the boolean 'showModal' becomes true. Therefore, the command for actually showing the Modal which is the 'showHideModal' function in the given code must be a part of the next loop. While the openModal function is running, the setTimeout will set its calling function at the end of the loop, i.e. after the current function call stack is over. Therefore, we also need to pass in the context of the showHideModal function for it to run in the given scope. So, we use a variable 'that' and set it to the execution context 'this' of the class in which it is defined and pass that.showHideModal in the setTimeout. So, by the time setTimeout executes its callback function, the *ngIf condition has become true and the modal is present in the DOM to be shown or hidden by the 'showHideModal' function. Hope it helped! :)

doivosevic commented 5 years ago

Seeing as this is the first google result for this generic issue I would like to point out that the fix for me was to change target back to es5 from es6 related to https://github.com/angular/material2/issues/13695

therence11 commented 4 years ago

TypeError: Cannot read property 'nativeElement' of undefined at MaptransportPage.ngOnInit (maptransport.page.ts:43)

C'est l'erreur que je rencontre dans l’exécution de mon code

import { Component, OnInit, ViewChild } from '@angular/core'; import { ActionSheetController, Platform, AlertController } from '@ionic/angular'; import { GoogleMaps, GoogleMap, GoogleMapsEvent, GoogleMapOptions, CameraPosition, MarkerOptions, Marker, Environment, MyLocation, GoogleMapsAnimation } from '@ionic-native/google-maps';

import { LoadingController } from '@ionic/angular';

@Component({ selector: 'app-maptransport', templateUrl: './maptransport.page.html', styleUrls: ['./maptransport.page.scss'], }) export class MaptransportPage implements OnInit { @ViewChild('map') mapElement: any; map: GoogleMap; loading: any;

constructor( public alertController: AlertController, public actionCtrl: ActionSheetController, private platform: Platform,
private loadingCtrl: LoadingController ) { if (this.platform.is('cordova')) { this.loadMap(); }

}

ngOnInit() { this.mapElement = this.mapElement.nativeElement;

this.mapElement.style.width = this.platform.width() + 'px';
this.mapElement.style.height = this.platform.height() + 'px';

this.loadMap();

}

async loadMap() { this.loading = await this.loadingCtrl.create({message: 'Patientez Svp...' }); await this.loading.present();

Environment.setEnv({
  API_KEY_FOR_BROWSER_RELEASE: 'AIzaSyA6ohTVr2poCuSWxZznl_GTz25eYiL2wD8',
  API_KEY_FOR_BROWSER_DEBUG: 'AIzaSyA6ohTVr2poCuSWxZznl_GTz25eYiL2wD8'
});

const mapOptions: GoogleMapOptions = {
  controls: {
    zoom: false
  }
};

this.map = GoogleMaps.create(this.mapElement, mapOptions);

try {
  await this.map.one(GoogleMapsEvent.MAP_READY);

  this.addOriginMarker();
}catch(error) {
  console.log(error);
}

}

//Script pour afficher la position de l'utilisateur à l'aide d'un marker sur la carte; async addOriginMarker() { try { const myLocation: MyLocation = await this.map.getMyLocation();

  await this.map.moveCamera({
    target: myLocation.latLng,
    zoom: 18
  });

  this.map.addMarkerSync({
    title: 'Origem',
    icon: '#000',
    animation: GoogleMapsAnimation.DROP,
    position: myLocation.latLng
  });

}catch (error) {
  console.error(error);
}finally {
  this.loading.dismiss();
}  

}

async transportOptions() { const actionSheet = await this.actionCtrl.create({ buttons: [{ text: 'Choix de Transport', icon: 'share', handler: () => { console.log('routerLink="/choixtransport" '); } }, { text: 'Offres de Transport', icon: 'reader', handler: () => { console.log('Play clicked'); } }, { text: 'Favorite', icon: 'heart', handler: () => { console.log('Favorite clicked'); } }, { text: 'Annuler', icon: 'close', role: 'cancel', handler: () => { console.log('Cancel clicked'); } }] }); await actionSheet.present(); }

}