nativescript-angular: TypeError: Cannot read property 'destroyed' of null

<ListView class="list-group" [items]="albums"  >       
     <ng-template let-album="item">
         <StackLayout (loaded)="loadComplete()" class="list-group-item">
        <Label  [text]="album.name" class=" list-group-item-heading"></Label>
        <Label  class="list-group-item-text" text="{{album.images.length}} Images"></Label>  
        <WrapLayout *ngIf="album.images.length>0" width="100%"  class="list-group" >
               <WebImage  stretch="aspectFit"  backgroundColor="gray" 
                   [src]="image.thumbnailUrl" *ngFor="let image of album.images"
                   width="20%" [height]="imageElement.getActualSize().width"  #imageElement></WebImage>
         </WrapLayout >
      </StackLayout>
    </ng-template>
</ListView>

if we use above code then app is starting without any errors and output is as expected.

but using below code i get the error that "ERROR TypeError: Cannot read property ‘images’ of null "

<ScrollView class="list-group"   >       
     <StackLayout>
         <StackLayout *ngFor="let album of albums" (loaded)="loadComplete()" class="list-group-item">
           <Label  [text]="album.name" class=" list-group-item-heading"></Label>
           <Label  class="list-group-item-text" text="{{album.images.length}} Images"></Label>  
           <WrapLayout *ngIf="album.images.length>0" width="100%"  class="list-group" >
               <WebImage  stretch="aspectFit"  backgroundColor="gray" 
                   [src]="image.thumbnailUrl" *ngFor="let image of album.images"
                   width="20%" [height]="imageElement.getActualSize().width"  #imageElement></WebImage>
            </WrapLayout >
         </StackLayout>
    </StackLayout>
</ScrollView>

we can clearly state that both code are equivalent and should give similar output. but last gives error “ERROR TypeError: Cannot read property ‘images’ of null”

Detailed error is like this:

JS: ERROR CONTEXT [object Object]
JS: ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'destroy
ed' of null
JS: TypeError: Cannot read property 'destroyed' of null
JS:     at ViewContainerRef_.move (file:///data/data/org.nativescript.bunkerzon/
files/app/tns_modules/@angular/core/bundles/core.umd.js:11506:20)
JS:     at file:///data/data/org.nativescript.bunkerzon/files/app/tns_modules/@a
ngular/common/bundles/common.umd.js:2652:38
JS:     at DefaultIterableDiffer.forEachOperation (file:///data/data/org.natives
cript.bunkerzon/files/app/tns_modules/@angular/core/bundles/core.umd.js:7451:17)

JS:     at NgForOf._applyChanges (file:///data/data/org.nativescript.bunkerzon/f
iles/app/tns_modules/@angular/common/bundles/common.umd.js:2641:17)
JS:     at NgForOf.ngDoCheck (file:///data/data/org.nativescript.bunkerzon/files
/app/tns_modules/@angular/common/bundles/common.umd.js:2627:22)
JS:     at checkAndUpdateDirectiveInline (file:///data/data/org.nativescript.bun
kerzon/files/app/tns_modules/@angular/core/bundles/core.umd.js:12410:19)
JS:     at checkAndUpdateNodeInline (file:///data/data/org.nativescript.bunkerzo
n/files/app/tns_modules/@angular/core/bundles/core.umd.js:13931:20)
JS:     at checkAndUpdateNode (file:///data/data/org.nativescript.bunkerzon/file
s/app/tns_modules/@angular/core/bundles/core.umd.js:13874:16)
JS:     at debugCheckAndUpdateNode (file:///data/data/org.nativescript.bunkerzon
/files/app/tns_modules/@angular/core/bundles/core.umd.js:14767:76)
JS:     at debugCheckDirectivesFn (file:///data/data/org.nativescript.bunkerzon/
files/app/tns_modules/@angular/core/bundles/core.umd.js:14708:13)
JS:     at Object.eval [as updateDirectives] (ng:///HomeModule/HomeComponent.ngf
actory.js:89:9)
JS:     at Object.debugUpdateDirectives [as updateDirectives] (file:///data/data
/org.nativescript.bunkerzon/files/app/tns_modules/@angular/core/bundles/core.umd
.js:14693:21)
JS:     at checkAndUpdateView (file:///data/data/org.nativescript.bunkerzon/file
s/app/tns_modules/@angular/core/bundles/core.umd.js:13840:14)
JS:     at callViewAction (file:///data/data/org.nativescript.bunkerzon/files/ap
p/tns_modules/@angular/core/bundles/core.umd.js:14191:21)
JS:     at execEmbeddedViewsAction (file:///data/data/org.nativescript.bunkerzon
/files/app/tns_modules/@angular/core/bundles/core.umd.js:14149:17)
JS: synct contact success [object Object]
JS: ERROR TypeError: Cannot read property 'images' of null
JS: ERROR CONTEXT [object Object]

i had also tried binding label text to function in which local album variable gets printed like below

<Label [text]="conDir(group)" class=" list-group-item-heading"></Label>

conDir(a){ console.log(a.id) console.log(a.name) return a.name; }

Looking at logs i can verify that “album” is not null and has correct value as it should have. so i have no idea what can be wrong.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 19 (9 by maintainers)

Most upvoted comments

@NathanaelA While writing the PR and tests, the problem seems to be with nativescript-zone.

If you trigger fixture.detectChanges() or fixture.autoDetectChanges(true), the problem does not happen, but if you trigger fixture.autoDetectChanges(true) and add a setTimeout you’ll get the following error:

TypeError: Cannot read property 'id' of null--Pendng async tasks are: [type: macroTask, source: setTimeout, args: {handleId:37,isPeriodic:false,delay:1000,args:[object Arguments]},type: macroTask, source: setTimeout, args: {handleId:39,isPeriodic:false,delay:0,args:[object Arguments]}]

Angular has an “NgZone” which contains an inner zone (actual angular zone) and outer zone (code running without angular “knowning”). The code from onItemLoading is called on the <root> zone (since it’s being bound by on and not by (event)=...). The zones in NS core seem to be doing:

const zone = Zone.current;
zone.run(callback);

which means if a request came from the angular zone, Zone.current.name is angular, so it’ll trigger CD when it’s finished.

Now for some reason, this bug only happens when you have other tasks queued up (which is often the case in real apps), so it can’t be reproduced in unit testing, which probably means the issue is not with running the code inside the angular zone, but something with zone itself. My solution of running it inside the ngzone is actually more of a workaround.

On all cases, onItemLoading was called in zone <root>.

After over 1 year of thinking about this on and off I FINALLY found the solution, and the problem is way deeper than I originally thought.

The problem is specifically with onItemLoading.

  • onItemLoading calls view.detectChanges() inside a Zone, but NOT THE ANGULAR ZONE
  • ngFor starts detecting changes
  • ngFor creates/deletes views
  • view call loaded/unloaded
  • renderer enters the angular zone and calls the callbacks
  • renderer leaves the zone
  • zone checks if it’s stable (nothing else running in the angular zone). Triggers change detection again. Remeber, view.detectChanges() is running in a zone, but not in the angular zone
  • second change detection running simultaneously with the first
  • ngFor (inside ngzone) starts checking it’s changes again, finishes without a problem
  • ngFor (outside ngzone) finishes running it’s change detection, but it’s references are dirty, so it throws errors.

This happens for 2 reasons:

  1. Nativescript events are never delayed, which means loaded and others will always trigger when they happened, even if there are other tasks running. This is different from the web event loop, where the event callbacks are queued image 1.1. This means you can intercept events quickly and act upon them, but they WILL impact applications in unforeseeable ways. 1.2. If you implement a callback queue, like I did in my testing, you’ll miss events like unloaded because the element will have been destroyed by then, as it happens DURING the destroy. This resulted in my (unloaded) never firing as it waited for the whole destruction process to fire, and by then the subscription had been closed.
  2. Some angular calls are not running in the angular zone. When they eventually enter and leave the ngzone, it’ll trigger CD during CD, causing all sorts of issues.

This discovery raises some issues:

  1. all core polyfills (setTimeout, etc) are using Zone, but I’m not sure they’re running on the Angular zone. I believe most of them are, but my newest addition requestAnimationFrame has not been patched in zone-nativescript https://github.com/NativeScript/nativescript-angular/blob/master/nativescript-angular/zone-js/dist/zone-nativescript.js#L1676
  2. there are other places in nativescript-angular where detectChanges or markForCheck are being called outside the ngzone. That I could find: dialogs, DetachedLoader, and templated-items-comp.

I’ll write a PR wrapping all detectChanges in the ngzone for the time being, but the other points must be considered.

Ok, I found why this error occurs. Wrapping the callback in NgZone.run breaks the code.

See: https://play.nativescript.org/?template=play-ng&id=hpl6OJ&v=6

            this.el.nativeElement.on("loaded", this.getZonedCb((evt: EventData) => {
                this.safeLoaded.emit(evt);
            }));
    getZonedCb(callback: any) {
        return (...args) => {
            this.zone.run(() => {
                callback.apply(undefined, args);
            });
        };
    }

this is the same logic being used in nativescript-angular renderer https://github.com/NativeScript/nativescript-angular/blob/309ed7b0de824a869b1b1c07b4a25928f7a754e1/nativescript-angular/renderer.ts#L296

It seems ngzone tries to check if the element is destroyed, but at the time of loading and unloading, it’s null, so it throws the error.

Edit:

More testing. Using a setTimeout 0 to make it run on the next angular cycle also fixes the problem:

    getZonedCb(callback: any) {
        return (...args) => {
            setTimeout(() => {
                this.zone.run(() => {
                    callback.apply(undefined, args);
                });
            }, 0);
        };
    }

AND the item is available on load.

Forcing a change detection with this.cdref.detectChanges(); crashes immediately with TypeError: cannot read property 'item' of null (seems related to the error when you run with ngzone).

Some things seem to be happening out of order, so when we try to enter Angular’s zone, not everything is ready yet, so it breaks.

Test project with setTimeout: https://play.nativescript.org/?template=play-ng&id=hpl6OJ&v=7

Edit 2:

After giving it some more thought, it seems the loaded event is called just after ViewUtil.CreateView, while the attributes haven’t even been set yet. Unloaded is called in ViewBase._removeView, inside removeFromVisualTree. This all happens while angular is still rendering and the attributes aren’t set yet. setTimeout with 0 makes the event fire after all the event loop has been processed.

So our options probably are either making these events run outside of angular, and it’s up to the developer to use timeouts, or setting the timeout inside the listen which means the event won’t be called in the exact moment it happens (already true for loaded, anyway).

hii @tsonevn , there were no issue with WebImage. i had found problem and solved it. i’m also adding full information if anyone may need it in future.

<ListView class="list-group" [items]="albumObj.albums"  >       
     <ng-template let-album="item">
         <StackLayout (loaded)="loadComplete()" class="list-group-item">
        <Label  [text]="album.name" class=" list-group-item-heading"></Label>
        <Label  class="list-group-item-text" text="{{album.images.length}} Images"></Label>  
        <WrapLayout *ngIf="album.images.length>0" width="100%"  class="list-group" >
               <WebImage  stretch="aspectFit"  backgroundColor="gray" 
                   [src]="image.thumbnailUrl" *ngFor="let image of album.images"
                   width="20%" [height]="imageElement.getActualSize().width"  #imageElement></WebImage>
         </WrapLayout >
      </StackLayout>
    </ng-template>
</ListView>

in above code albumObj is shared service which has “albums” named array. this service initially fetch previous Albums from the local database. and also request fro new albums data and updates it.

i had getting error i had mentioned in previous comment.

loadComplete(){
    this.cd.detectChanges();
}

above function was real culprit. if i remove the this.cd.detectChanges() code runs without any error. but doing changeDetection on StackLayouts loaded event and running code i was getting above error and in html file “album” object becomes null. but Note that while logging checking “albumObj.albums” on 100milisecond time interval i can see that it has perfect value and all albums has images property.

So in conclusion i can say that when using this.cd.detectChanges() in StackLayouts loaded event it destroys objects value in html view. but works perfectly in TS file. also i really think there is something wrong “loaded” event or “change Detection Ref” please look into this if possible.