This demo uses imperative chart updates. The generated code is a faithful Angular translation: open it in CodeSandbox to run and tweak.

import { Component, AfterViewInit, OnDestroy, ViewChild } from '@angular/core';
import {
  ChartComponent,
  ApexAxisChartSeries,
  ApexNonAxisChartSeries,
  ApexChart,
  ApexXAxis,
  ApexYAxis,
  ApexTitleSubtitle,
  ApexDataLabels,
  ApexStroke,
  ApexFill,
  ApexLegend,
  ApexTooltip,
  ApexMarkers,
  ApexPlotOptions,
  ApexResponsive,
  ApexGrid,
  ApexAnnotations,
  ApexStates,
  ApexTheme,
  NgApexchartsModule,
} from 'ng-apexcharts';

export type ChartOptions = {
  series?: ApexAxisChartSeries | ApexNonAxisChartSeries;
  chart?: ApexChart;
  xaxis?: ApexXAxis;
  yaxis?: ApexYAxis | ApexYAxis[];
  title?: ApexTitleSubtitle;
  subtitle?: ApexTitleSubtitle;
  dataLabels?: ApexDataLabels;
  stroke?: ApexStroke;
  fill?: ApexFill;
  legend?: ApexLegend;
  tooltip?: ApexTooltip;
  markers?: ApexMarkers;
  plotOptions?: ApexPlotOptions;
  responsive?: ApexResponsive[];
  grid?: ApexGrid;
  annotations?: ApexAnnotations;
  states?: ApexStates;
  theme?: ApexTheme;
  colors?: string[];
  labels?: any;
};

@Component({
  selector: 'app-chart',
  standalone: true,
  imports: [NgApexchartsModule],
  templateUrl: './chart.component.html',
})
export class AppChart implements AfterViewInit, OnDestroy {
  @ViewChild('chart') chart!: ChartComponent;
  private COMMUTES: any = (function () {
          var seed = 20260813
          function rand() {
            seed = (seed * 16807) % 2147483647
            return (seed - 1) / 2147483646
          }
          // Box-Muller into a log-normal: journey times are right-skewed, since a trip
          // can go badly wrong but cannot finish in less than no time.
          function trips(n, mu, sigma) {
            var out = []
            for (var i = 0; i < n; i++) {
              var u1 = Math.max(rand(), 1e-9)
              var u2 = rand()
              var z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2)
              out.push(Math.round(Math.exp(mu + z * sigma)))
            }
            return out
          }
          return {
            // Driving is quicker on a typical day and far less predictable: a lower
            // centre, a much heavier tail.
            car: trips(900, 3.25, 0.5),
            transit: trips(900, 3.45, 0.2),
          }
        })();

  private median = (values: any): any => {
          var sorted = values.slice().sort(function (a, b) {
            return a - b
          })
          var mid = Math.floor(sorted.length / 2)
          return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
        };

  private worstTwentieth = (values: any): any => {
          var sorted = values.slice().sort(function (a, b) {
            return a - b
          })
          return sorted[Math.floor((sorted.length - 1) * 0.95)]
        };

  private renderStats = (): any => {
          var el = document.querySelector('#stats')
          if (!el) return
          el.innerHTML =
            'Car: typical <b>' +
            this.median(this.COMMUTES.car) +
            ' min</b>, ' +
            'bad day <b>' +
            this.worstTwentieth(this.COMMUTES.car) +
            ' min</b> &middot; ' +
            'Transit: typical <b>' +
            this.median(this.COMMUTES.transit) +
            ' min</b>, ' +
            'bad day <b>' +
            this.worstTwentieth(this.COMMUTES.transit) +
            ' min</b>'
        };

  private wireComparisonControls = (chart: any): any => {
          var buttons = [].slice.call(document.querySelectorAll('[data-overlap]'))
  
          buttons.forEach(function (b) {
            b.addEventListener('click', function () {
              var overlap = b.getAttribute('data-overlap') === 'true'
              buttons.forEach(function (other) {
                other.className = other === b ? 'on' : ''
              })
              chart.updateOptions({
                plotOptions: { histogram: { overlap: overlap } },
                // The defaults that come with an overlay are ordinary defaults, so a
                // runtime switch has to carry them itself.
                fill: { opacity: overlap ? 0.65 : 0.85 },
                stroke: overlap
                  ? { show: false }
                  : { show: true, width: 1, colors: ['#fff'] },
              })
            })
          })
  
          this.renderStats()
        };

  public chartOptions: Partial<ChartOptions> = {
          series: [
            {
              name: 'Car',
              data: this.COMMUTES.car,
            },
            {
              name: 'Transit',
              data: this.COMMUTES.transit,
            },
          ],
          chart: {
            id: 'commutes',
            type: 'histogram',
            // Fixed width, not responsive: thin bars make every bar edge a hairline, so a
            // page-width nudge of a pixel or two visibly moves the whole distribution.
            width: 700,
            height: 400,
            toolbar: {
              show: false,
            },
          },
          plotOptions: {
            histogram: {
              bins: 'auto',
              // The default with more than one series. Every distribution is drawn across
              // the full bin so they lie on top of one another; set false for side-by-side
              // bars. All series share one set of bin edges either way.
              overlap: true,
            },
          },
          colors: ['#f2a43a', '#5d6d9e'],
          xaxis: {
            title: {
              text: 'Door-to-door time (minutes)',
            },
            labels: {
              formatter: (val) => {
                return Math.round(val)
              },
            },
          },
          yaxis: {
            title: {
              text: 'Trips',
            },
          },
          legend: {
            position: 'top',
            horizontalAlign: 'right',
          },
        };
  ngAfterViewInit() {
    (window as any).ApexCharts.setLicense('APEX-eyJleHBpcnlEYXRlIjoiMjEyNi0wNy0wNCIsImlzc3VlRGF0ZSI6IjIwMjYtMDctMjgiLCJwbGFuIjoicHJlbWl1bSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiLCJsb2NhbGhvc3QiXSwic2lnIjoieVBmb1VCc0Z3TU9ZdUEyaEZkR0I2Y1FtZ0JITUtXcVdJSjB2NVRESXRZbFR3eDJMUmh6R2x0RUc3VXJ4X0s3b25ZMWRZb2Z2VGItN01ydFYyNDVyOWcifQ==');
    this.wireComparisonControls(this.chart)

    function wireComparisonControls(chart) {
            var buttons = [].slice.call(document.querySelectorAll('[data-overlap]'))

            buttons.forEach(function (b) {
              b.addEventListener('click', () => {
                var overlap = b.getAttribute('data-overlap') === 'true'
                buttons.forEach(function (other) {
                  other.className = other === b ? 'on' : ''
                })
                this.chart.updateOptions({
                  plotOptions: { histogram: { overlap: overlap } },
                  // The defaults that come with an overlay are ordinary defaults, so a
                  // runtime switch has to carry them itself.
                  fill: { opacity: overlap ? 0.65 : 0.85 },
                  stroke: overlap
                    ? { show: false }
                    : { show: true, width: 1, colors: ['#fff'] },
                })
              })
            })

            this.renderStats()
          }
  }

  ngOnDestroy() {
    // no cleanup needed
  }
}
Comparing Distributions - Angular Histogram Charts | ApexCharts.js | ApexCharts.js