Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the right way to implement a 3rd party library that manipulates the Dom?

I am using a 3rd party library to render a widget onto the screen (Okta's SignInWidget). The way the widget is rendered is so:

this.oktaSignInWidget.renderEl(
    { el: '#widget-container' },
    () => {},
    err => {
      console.error(err);
    }
  );

My initial thought was to put this into a directive, but even with directives you are supposed to let Renderer2 do the rendering. Is there a best practice here?

like image 619
Doug S. Avatar asked Dec 31 '18 18:12

Doug S.


1 Answers

As the widget probably replaces/builds the content of the given container, this seems to be a classic use-case for a regular Angular component. It seems you also don't need dynamic instantiation (which is often found in 3rd party library inclusion tutorials).

If you create a component with the selector myapp-login you may just place it into some of your login pages:

<h1>Login</h1>
<p>Hi people, login to get more features:</p>
<myapp-login></myapp-login>

Roughly following this Angular University's article about @ViewChild (which briefly mentions third party libs too), this SO answer or the article Using 3rd Party Library Inside Angular2 by Netanel Basal (omitting the inputs) we may proceed as follows:

To get access to an element in your custom component, you should let Angular inject a reference to an element tagged with #container by adding an ElementRef typed property decorated with @ViewChild('container').

@ViewChild('container')
container: ElementRef;

In the component's constructor you may construct your widget instance.

After your view got initialized, you should let the widget instance render itself into the container element. Remember to destroy the widget when the component gets destroyed (perhaps by calling .remove()).

This is an untested skeleton for your login component:

...
import OktaSignIn from '@okta/okta-signin-widget';
import '@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
import '@okta/okta-signin-widget/dist/css/okta-theme.css';

@Component({
  selector: 'myapp-login',
  template: '<div #container></div>'
})
export class Login implements AfterViewInit, OnDestroy {
  @ViewChild('container')
  container: ElementRef;

  oktaSignInWidget: OktaSignIn;

  constructor() {
    this.oktaSignInWidget = new OktaSignIn({baseUrl: 'https://{yourOktaDomain}'});
  }

  ngAfterViewInit() {
    const containerElem = this.container.nativeElement;
    this.oktaSignInWidget.renderEl(
      { el: containerElem },
      response => {}, // success callback
      error => {} // error callback 
    );
  }

  ngOnDestroy() {
    if (this.oktaSignInWidget) {
      this.oktaSignInWidget.remove();
      this.oktaSignInWidget = null;
    }
  }
}

You should move the basic configuration of the domain and other environment specific data to a special file (environment.ts), like here.

If anything meaningful happens after the login attempt, you should create a service which handles the authentication state and delegate the (transformed) results to it.

like image 141
Hero Wanders Avatar answered Nov 10 '22 19:11

Hero Wanders