Closed
Description
<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.
Activity
tsonevn commentedon 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 commentedon 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 commentedon 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 commentedon 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 apptns run <platoform name>
.[-]ts objects gets null value when using *ngFor [/-][+]TypeError: Cannot read property 'destroyed' of null[/+]bhavincb commentedon Mar 23, 2018
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.
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.
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 commentedon Jul 11, 2018
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 commentedon 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 commentedon Jul 12, 2018
@tsonevn Here you go:
https://play.nativescript.org/?template=play-ng&id=hpl6OJ
edusperoni commentedon Aug 10, 2018
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:
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 commentedon Aug 13, 2018
HI @edusperoni,
If you are facing a problem with RadListView, please log a new issue in nativescript-ui-feedback repository.
irrfan13 commentedon Aug 16, 2018
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 commentedon Sep 18, 2018
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 commentedon 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 ofloaded
?edusperoni commentedon Sep 19, 2018
I can confirm layoutChanged doesn't trigger the error. So far, it only seems to happen on
loaded
andunloaded
.Added layoutchanged to example:
https://play.nativescript.org/?template=play-ng&id=hpl6OJ&v=5
edusperoni commentedon 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 is the same logic being used in nativescript-angular renderer
nativescript-angular/nativescript-angular/renderer.ts
Line 296 in 309ed7b
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:
AND the item is available on load.
Forcing a change detection with
this.cdref.detectChanges();
crashes immediately withTypeError: 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 inViewBase._removeView
, insideremoveFromVisualTree
. 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 commentedon Jan 27, 2020
May be following can help
https://stackoverflow.com/a/46407516/9297920
for me it worked
edusperoni commentedon Feb 12, 2020
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
callsview.detectChanges()
inside a Zone, but NOT THE ANGULAR ZONEview.detectChanges()
is running in a zone, but not in the angular zoneThis happens for 2 reasons:
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 queued1.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.This discovery raises some issues:
requestAnimationFrame
has not been patched inzone-nativescript
https://github.com/NativeScript/nativescript-angular/blob/master/nativescript-angular/zone-js/dist/zone-nativescript.js#L1676nativescript-angular
wheredetectChanges
ormarkForCheck
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 commentedon Feb 12, 2020
edusperoni commentedon Feb 12, 2020
@NathanaelA While writing the PR and tests, the problem seems to be with
nativescript-zone
.If you trigger
fixture.detectChanges()
orfixture.autoDetectChanges(true)
, the problem does not happen, but if you triggerfixture.autoDetectChanges(true)
and add asetTimeout
you'll get the following error: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 byon
and not by(event)=...
). The zones in NS core seem to be doing:which means if a request came from the angular zone,
Zone.current.name
isangular
, 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>
.ChangeDetectorRef#detectChanges()
causes error inNgForOf
directive angular/angular#45417