AngularEditorLoader.tsx 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import React from 'react';
  2. import { AnnotationQuery, DataSourceApi } from '@grafana/data';
  3. import { AngularComponent, getAngularLoader } from '@grafana/runtime';
  4. export interface Props {
  5. annotation: AnnotationQuery;
  6. datasource: DataSourceApi;
  7. onChange: (annotation: AnnotationQuery) => void;
  8. }
  9. interface ScopeProps {
  10. ctrl: {
  11. currentDatasource: DataSourceApi;
  12. currentAnnotation: AnnotationQuery;
  13. ignoreNextWatcherFiring: boolean;
  14. };
  15. }
  16. export class AngularEditorLoader extends React.PureComponent<Props> {
  17. ref: HTMLDivElement | null = null;
  18. angularComponent?: AngularComponent;
  19. scopeProps?: ScopeProps;
  20. componentWillUnmount() {
  21. if (this.angularComponent) {
  22. this.angularComponent.destroy();
  23. }
  24. }
  25. componentDidMount() {
  26. if (this.ref) {
  27. this.loadAngular();
  28. }
  29. }
  30. componentDidUpdate(prevProps: Props) {
  31. if (prevProps.datasource !== this.props.datasource) {
  32. this.loadAngular();
  33. }
  34. if (this.scopeProps && this.scopeProps.ctrl.currentAnnotation !== this.props.annotation) {
  35. this.scopeProps.ctrl.ignoreNextWatcherFiring = true;
  36. this.scopeProps.ctrl.currentAnnotation = this.props.annotation;
  37. this.angularComponent?.digest();
  38. }
  39. }
  40. loadAngular() {
  41. if (this.angularComponent) {
  42. this.angularComponent.destroy();
  43. this.scopeProps = undefined;
  44. }
  45. const loader = getAngularLoader();
  46. // NOTE: BE CAREFUL HERE
  47. // If this template contains an ng-if, then it won't be removed correctly by AngularLoader.
  48. // The compiledElem will only contain the single comment node (e.g. <!-- ngIf !ctrl.currentDatasource.annotations -->)
  49. const template = `<plugin-component type="annotations-query-ctrl"> </plugin-component>`;
  50. const scopeProps = {
  51. ctrl: {
  52. currentDatasource: this.props.datasource,
  53. currentAnnotation: this.props.annotation,
  54. ignoreNextWatcherFiring: false,
  55. },
  56. };
  57. this.angularComponent = loader.load(this.ref, scopeProps, template);
  58. this.angularComponent.digest();
  59. this.angularComponent.getScope().$watch(() => {
  60. // To avoid recursive loop when the annotation is updated from outside angular in componentDidUpdate
  61. if (scopeProps.ctrl.ignoreNextWatcherFiring) {
  62. scopeProps.ctrl.ignoreNextWatcherFiring = false;
  63. return;
  64. }
  65. this.props.onChange(scopeProps.ctrl.currentAnnotation);
  66. });
  67. this.scopeProps = scopeProps;
  68. }
  69. render() {
  70. return <div ref={(element) => (this.ref = element)} />;
  71. }
  72. }