Skip to content

TypeError: Cannot read property 'destroyed' of null #1221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bhavincb opened this issue Mar 5, 2018 · 19 comments · Fixed by #2121
Closed

TypeError: Cannot read property 'destroyed' of null #1221

bhavincb opened this issue Mar 5, 2018 · 19 comments · Fixed by #2121
Labels

Comments

@bhavincb
Copy link
Contributor

bhavincb commented Mar 5, 2018

<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.

@tsonevn
Copy link
Contributor

tsonevn commented Mar 6, 2018

Hi, @bhavincb,
Could you provide sample project, which demonstrates the problem with the ngFor?
The issue might be related to #1206.

@bhavincb
Copy link
Contributor Author

bhavincb commented Mar 6, 2018

hii @tsonevn ,
At the moment i'm unable to provide sample project because problem we are facing is inside very large project so separating it will take some time.
but as i said i had implemented 2 UI html which should give same output, but second one which is implemented using "ScrollView" and "*ngFor" gives the error i had mentioned above, while first implementation using "ListView" and "ng-template" works perfectly fine.
There is not a single changes made to TS files only HTML files changed , so i can say that "albums" object generated remains same in both cases, and i also checked same by debugging app in chrome dev tools.

@bhavincb
Copy link
Contributor Author

bhavincb commented Mar 6, 2018

hi @tsonevn ,
As you had mentioned that there is some issue with *ngIf while using conditions , so i had replaced *ngIf with "visibility", and also tried removing *ngIf but still it is not working and giving same error.

@tsonevn
Copy link
Contributor

tsonevn commented Mar 8, 2018

Hi, @bhavincb,
I notice that you are using nativescript-web-image-cache plugin in your application.

Regarding that, could you replace WebImage with the Image component provided by NativeScript core modules and check if the issue is related to the plugin's component. After replacing the WebImage, remove the platform with tns platform remove <platform name> and rebuild the app tns run <platoform name>.

@bhavincb bhavincb changed the title ts objects gets null value when using *ngFor TypeError: Cannot read property 'destroyed' of null Mar 23, 2018
@bhavincb
Copy link
Contributor Author

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.

@edusperoni
Copy link
Collaborator

We're having the same issue. As soon as we hook a "loaded" listener, all hell breaks loose. We're using a directive to add some attributes to a TextField on Android, no change detection at all. Even just binding a (loaded)="doesNothing()" is enough to trigger this error.

If we use a single Label inside the for and set (loaded)=printThis(item) we don't get a crash, but we do get an error accessing "item" and the function prints "null". Immediately after it prints the correct object, as if the Label was loaded twice.

I can set up a playground if needed.

@tsonevn
Copy link
Contributor

tsonevn commented Jul 12, 2018

Hi @edusperoni,
Can you provide a sample project and more info about your environment(CLI, tns-core-modules, nativescript-angular, runtime versions)?

@edusperoni
Copy link
Collaborator

@edusperoni
Copy link
Collaborator

I'd also like to add that this issue also happens using RadListView, but it's actually a little worse. We have recently started using RadListView with this template:

<ng-template (...)>
<MyComponent [item]="item.details"></MyComponent>
</ng-template>

and we use (loaded) inside this component. As soon as we changed from ListView to RadListView, it complained about not being able to read "details" of undefined and PullToRefresh stopped working for some reason. Adding a *ngIf="item" solves this, but seems a little too hacky.

Are there any updates on this issue?

@tsonevn
Copy link
Contributor

tsonevn commented Aug 13, 2018

HI @edusperoni,
If you are facing a problem with RadListView, please log a new issue in nativescript-ui-feedback repository.

@irrfan13
Copy link

I am also having same problem with ngx-pagination, while there is data available it works fine, if there is no data(null) then its replicating pagination page numbers.

@edusperoni
Copy link
Collaborator

I'd like to provide more info about this issue, in hopes it gets into the next release. As you may have seen, I've commented on #848 (comment) saying it's basically impossible to get the android view as it loads due to both these bugs.

After some testing, I've arrived to the conclusion it's the angular event binding specifically that causes this bug. I've updated my example to exemplify it:

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

If you use (loaded) and (unloaded), you get:

ERROR TypeError: Cannot read property 'item' of null

and ultimately a crash:

TypeError: Cannot read property 'destroyed' of null

but if you use my directive which adds safeLoaded and safeUnloaded. You get no error at all, and that's done with this.el.nativeElement.on("loaded",...).

If you start listening to the event on the directive constructor (set ensureItemExists = false), before the element is loaded, you still get

LOADED! null

But no ERROR TypeError: Cannot read property 'item' of null.

If you do it in ngAfterViewInit (set ensureItemExists = true), you sometimes have to manually trigger a loaded event in case the element hasn't loaded (this happens sometimes in android, as seen in #848), but once that is done, you get the item AND no crash/errors.

Maybe this is enough to shed some light on the issue?

@tsonevn
Copy link
Contributor

tsonevn commented Sep 19, 2018

Hi all,
With the latest NativeScript version, we introduced a new event layoutChanged. Can you check whether you will have the same problem while using layoutChanged instead of loaded?

@edusperoni
Copy link
Collaborator

I can confirm layoutChanged doesn't trigger the error. So far, it only seems to happen on loaded and unloaded.

Added layoutchanged to example:

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

@edusperoni
Copy link
Collaborator

edusperoni commented Sep 19, 2018

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

renderElement.on(eventName, zonedCallback);

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).

@voodark
Copy link

voodark commented Jan 27, 2020

May be following can help

https://stackoverflow.com/a/46407516/9297920
for me it worked

@edusperoni
Copy link
Collaborator

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.

@NathanaelA

This comment was marked as abuse.

@edusperoni
Copy link
Collaborator

@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>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants