ZupIT/beagle-web-angular

Using beagle getViewContentManager doesn't work in production mode with Ivy enabled

carolinegoncalveszup opened this issue · 2 comments

Beagle Angular version: 1.2.0

Steps To Reproduce

  1. Using a default beagle list view works properly in dev mode with Ivy enabled, but as soon as the application is build for prod mode and run the dist, the application doesn't work showing the error that getViewContentManager is not a function
    Related to angular/angular#8277

Link to code example:

The current behavior

Using any default component that uses getViewContentManager with ivy enabled and running the dist generated for prod

The expected behavior

Application should work properly in prod and dev mode when using beagle with Ivy

Using this.viewContainerRef._data?.componentView?.component doesn't work with ivy enabled because it was using a private api that changed with ivy as mentioned in angular/angular#36675
The approach using ng.getComponent does work, but only for dev mode as it is a debug tool that is not available in prod mode.

I solved the problem without the use of the directive and adding the function definitions in the BeagleComponent as the methods. This approach needed the BeagleProvider instance, so the component received one new arg that is the Injector from angular, with this injector it is possible to get BeagleProvider instance and the directive is no longer needed. Although this approach works, we do not want to have break changes. Considering this approach, any component that inherits from BeagleCompont must call the parent constructor with the new argument, the angular injector whereas the current version having the constructor in the child component is not mandatory and not arguments are necessary. Also, this approach couples even more the application component with the beagle angular library which might not be desirable.

I also did some coding to based on a new prop called configName that receives the key of that specific component in the components mapping get that component class. This also worked, but the result is just the class corresponding to that specific component and not the instance itself, which is what is needed for overriding the component function.

After some research on how to access host component from Directives it gets clear that it is a known issue for the angular team and community as @carolinegoncalveszup pointed out. It is also one without a concrete or oficial solution as shown in many forum threads.

I am going to leave docummented here some of the techniques I tried in case someone comes up with a new approach or wants to try something different.

The first thing I tried was creating a class myReference which only purpose was to be a token of access for the component itself, We can then add a provider along with the Component Decorator

@Component({
  providers: [{ provide: MyReference, useExisting: forwardRef(() => BeagleButtonComponent) }]
})

and finally Inject it in the directive

constructor(
    private myReference: MyReference
  ) 

The snippet above worked as expected but the problem was I would have to add a provider for each of the components, maybe if the case was only for our intern items it would be acceptable to replicate this code among them, but unfortunatelly it was necessary the handling of components from any application using Beagle, so a more generic approach was in need.
Following the same logic I tried adding the provider direct into BeagleComponent which is then extended for any components that would require access to the getViewContent() function. It did not work, and I could not see why maybe for some lack of understanding on how the angular's compiling system works

Another try was using viewChild and viewChildren to get access to the directives' host, this also ended up not working, mostly because I could not find a way to add a dynamic token in the template that could be used by the directive when it became available.

After these tries and some extra research I learned that we could inject the component itself in the directives' constructor, this is in my opinion the cleanest way of handling this problem and can be useful in many cases.

   constructor(
    private myReference: BeagleButtonComponent
  )

The down side to this solution is that we need to know beforehand the component to be injected, I even experimented using the BeagleComponent abstract class as the type but it did not work and the result was provider ending up null