Skip to content
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

getting reference to the new lazy loaded element #70

Open
TheILPlace opened this issue Nov 22, 2020 · 13 comments
Open

getting reference to the new lazy loaded element #70

TheILPlace opened this issue Nov 22, 2020 · 13 comments

Comments

@TheILPlace
Copy link

hi,
i am using *axLazyElementDynamic to lazy load dynamic component.
very similar to the dynamic demo in the docs. the configuration is an Array retrieved from the server.

the problem i am facing:
i need to grab a hold on the created element in my class, in order to set some attributed, and register to event emitters.

i've tried using ViewChildren, but cannot get a reference to the element. i've tried various lifecycle hooks (oninit, aftercontentini, afterviewinit) but the viewchildren variable is always undefined.

you're help is appriciated.

@tomastrajan
Copy link
Member

Hi @TheILPlace !

Why not just use [someData] and (someEvent) bindings in the template?

If not possible (maybe they are always different) then you can always fall back to querySelector('my-tag')

@TheILPlace
Copy link
Author

thanks for your reply.
the idea was to have a complete dynamic solution. the tag-name, source url, and also the input attributes...

even if resolving to querySelector, the question is... in which lifecycle hook to catch it ?

maybe you should consider adding an event/hook that will fire after the new dynamic component is created , so we could use that !
i've read about the load hooks, but am not sure that we resolve this issue

@tomastrajan
Copy link
Member

@TheILPlace that's a good point, emitting custom axElementLoaded event would make sense, would you fancy to submit a PR for this? 😉

@TheILPlace
Copy link
Author

@tomastrajan
working on it...
i thought of emitting the instance of the created component in the event emitter... to make life easier for the application using this feature.

@tomastrajan
Copy link
Member

@TheILPlace sounds good! Looking forward to the PR and wish you happy holidays (if applicable 😉)

@TheILPlace
Copy link
Author

TheILPlace commented Dec 25, 2020

@tomastrajan
well, i've spend a couple of hours on this issue.
it seems that event emitters are not supported for structural directives :(

so we have 2 options:

  1. the user will create a Subject, and pass it as an input to the lazyelemendDirective. and inside the directive we will "next" the created element.
    the user will subscribe to his own subject.
    one problem with this - if you want to reach the created elements attributes, you need to wait for the changeDetection cycle to finish .. with delay(0) or setTimeout
    this.elementCreated.pipe(delay(0), take(1)).subscribe((s) => { }

  2. add a subject to the lazy-element-loader-service, and let the users subscribe to a function that will return the subject with already a pipe with delay(0) and take(1).
    the user will have to inject the service and subscribe.
    problem: doesnt support a scenario of more than one lazycompoment on the same component.
    in order to support more than one, we need to enhance the subject to emit an interface with the selector name, and the created element (or maybe get the selector from the element itself)

i've tested scenario 1 and it works quite well.

and as opposed to what i've suggested above, i cannot return a reference to the Component , but rather the html element
by using: this.viewRef.rootNodes[0]

what do you think ?

@tomastrajan
Copy link
Member

Hi @TheILPlace !

Thank you for all the effort and research. It's really unfortunate that there seems to be no really nice way of doing this on the library side.

I would rather not add some pretty sub-optimal ways of doing this in the lib itself.

One thing that comes to mind is then elements providing loaded event themselves.

eg <my-org-webcomponent *axLazyElement="url" (loaded)="onLoadedHandler($event)"> but this will only work for the elements you have full control of so you can add this yourself.

What about, instead of using subject if we passed in callback, eg <my-org-webcomponent *axLazyElement="url;onLoaded:myOnLoadedHandler"> would that work better than subject in terms of change detection, also element loads (or fails) only once such callback can have standard node-like interface with cb(err, element), thoughts?

@TheILPlace
Copy link
Author

@tomastrajan
hi !
the way of having a subject as an input was found after some research and found that as a solution for structural directives.
anyways, having a callback as an input should work the same way, i presume.
so i will give it a try later, and post my results (ease of use, and importantly, the change detection issue)

@TheILPlace
Copy link
Author

@tomastrajan
ok, i've tested this. passed a callback function to the *axLazyElementDynamic .
we don't need anything to be passed from the axLazyElementDynamic . just calling us after the element was created.

there is a still problem with the change detection cycle

<ax-lazy-element *axLazyElementDynamic="customElement.selector,
url: customElement.url; module: false; loadingTemplate: loading; loaded: elementCreated "
[data] = "myData"

setting a breakpoint in the "elementCreated" function, i get a reference to the created element by using:
this.dynamicComponent = this.elementRef.nativeElement.querySelector(this.customElement.selector);

      and checking: this.dynamicComponent.data  , the property is undefined.

putting this in a setTimeout with then show the actual value of the data property :(

@tomastrajan
Copy link
Member

@TheILPlace thank you for trying this out!

Now when I think about it, it seems reasonable that the binding was NOT executed yet when the element was loaded as that is really the first thing that happens and Angular runs after.

Maybe we could specify some other flag if the onLoaded callback should be timed out or not? eg
onLoadedWaitForBindings: boolean or just write it in the docs that this executes immediately when the webcomponent was loaded and hence developer needs to wait for bindings themselves, thoughts?

@TheILPlace
Copy link
Author

@tomastrajan
i've tried to have the setTimeout call inside the library and use that to wrapp the onLoaded emitter, but it doesnt work.
you have to write it yourself in your application code that loads the element.

about documentation - from experience, people first will try it, then find bindings are not present, then create an issue, and they you will point them to the docs :)

what about my suggestion to have a subject in the loader-service ? expose it via a function like:
elementLoaded(selector: string)
what will have a pipe(delay(0), take(1)) . so even no need to unsubscribe to it

@TheILPlace
Copy link
Author

@tomastrajan What do you think ?
having a callback being sent to the loaded input of the element (the user is responsible for using setTimeout , or expose a function in the loader-service (we will take care of the 'delay(0)' part ?

@tomastrajan
Copy link
Member

@TheILPlace service sounds good, but wasn't there some issue with multiple elements / tags or smthing?

problem: doesnt support a scenario of more than one lazycompoment on the same component.
in order to support more than one, we need to enhance the subject to emit an interface with the selector name, and the created element (or maybe get the selector from the element itself)

But if this can be resolved then it would be really nice!

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

No branches or pull requests

2 participants