<commit_before>/* eslint-disable no-empty-function */
/* eslint-disable no-useless-constructor */
/* eslint-disable @typescript-eslint/no-parameter-properties */
// We could use NgComponentOutlet here but there's currently no easy way
// to provide @Inputs and subscribe to @Outputs, see
// https://github.com/angular/angular/issues/15360
// For the time being, the ViewContainerRef approach works pretty well.
import {
  Component,
  Inject,
  OnInit,
  ViewChild,
  ViewContainerRef,
  ComponentFactoryResolver,
  OnDestroy,
  EventEmitter,
  SimpleChanges,
  SimpleChange,
  ChangeDetectorRef,
} from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
import { STORY } from '../app.token';
import { NgStory, ICollection } from '../types';

@Component({
  selector: 'storybook-dynamic-app-root',
  template: '<ng-template #target></ng-template>',
})
export class AppComponent implements OnInit, OnDestroy {
  @ViewChild('target', { read: ViewContainerRef })
  target: ViewContainerRef;

  subscription: Subscription;

  constructor(
    private cfr: ComponentFactoryResolver,
    private changeDetectorRef: ChangeDetectorRef,
    @Inject(STORY) private data: Observable<NgStory>
  ) {}

  ngOnInit(): void {
    this.data.pipe(first()).subscribe((data: NgStory) => {
      this.target.clear();
      const compFactory = this.cfr.resolveComponentFactory(data.component);
      const componentRef = this.target.createComponent(compFactory);
      const { instance } = componentRef;
      // For some reason, manual change detection ref is only working when getting the ref from the injector (rather than componentRef.changeDetectorRef)
      const childChangeDetectorRef: ChangeDetectorRef = componentRef.injector.get(
        ChangeDetectorRef
      );

      this.subscription = this.data.subscribe(newData => {
        this.setProps(instance, newData);
        childChangeDetectorRef.markForCheck();
        // Must detect changes on the current component in order to update any changes in child component's @HostBinding properties (angular/angular#22560)
        this.changeDetectorRef.detectChanges();
      });
    });
  }

  ngOnDestroy(): void {
    this.target.clear();
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  /**
   * Set inputs and outputs
   */
  private setProps(instance: any, { props = {} }: NgStory): void {
    const changes: SimpleChanges = {};
    const hasNgOnChangesHook = !!instance.ngOnChanges;

    Object.keys(props).forEach((key: string) => {
      const value = props[key];
      const instanceProperty = instance[key];

      if (!(instanceProperty instanceof EventEmitter) && (value !== undefined && value !== null)) {
        // eslint-disable-next-line no-param-reassign
        instance[key] = value;
        if (hasNgOnChangesHook) {
          changes[key] = new SimpleChange(undefined, value, instanceProperty === undefined);
        }
      } else if (typeof value === 'function' && key !== 'ngModelChange') {
        instanceProperty.subscribe(value);
      }
    });

    this.callNgOnChangesHook(instance, changes);
    this.setNgModel(instance, props);
  }

  /**
   * Manually call 'ngOnChanges' hook because angular doesn't do that for dynamic components
   * Issue: [https://github.com/angular/angular/issues/8903]
   */
  private callNgOnChangesHook(instance: any, changes: SimpleChanges): void {
    if (Object.keys(changes).length) {
      instance.ngOnChanges(changes);
    }<commit_msg>Call ngOnChanges() only for changed props<commit_after>/* eslint-disable no-empty-function */
/* eslint-disable no-useless-constructor */
/* eslint-disable @typescript-eslint/no-parameter-properties */
// We could use NgComponentOutlet here but there's currently no easy way
// to provide @Inputs and subscribe to @Outputs, see
// https://github.com/angular/angular/issues/15360
// For the time being, the ViewContainerRef approach works pretty well.
import {
  Component,
  Inject,
  OnInit,
  ViewChild,
  ViewContainerRef,
  ComponentFactoryResolver,
  OnDestroy,
  EventEmitter,
  SimpleChanges,
  SimpleChange,
  ChangeDetectorRef,
} from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
import { STORY } from '../app.token';
import { NgStory, ICollection } from '../types';

@Component({
  selector: 'storybook-dynamic-app-root',
  template: '<ng-template #target></ng-template>',
})
export class AppComponent implements OnInit, OnDestroy {
  @ViewChild('target', { read: ViewContainerRef })
  target: ViewContainerRef;

  readonly previousValues: { [key: string]: any } = {};

  subscription: Subscription;

  constructor(
    private cfr: ComponentFactoryResolver,
    private changeDetectorRef: ChangeDetectorRef,
    @Inject(STORY) private data: Observable<NgStory>
  ) {}

  ngOnInit(): void {
    this.data.pipe(first()).subscribe((data: NgStory) => {
      this.target.clear();
      const compFactory = this.cfr.resolveComponentFactory(data.component);
      const componentRef = this.target.createComponent(compFactory);
      const { instance } = componentRef;
      // For some reason, manual change detection ref is only working when getting the ref from the injector (rather than componentRef.changeDetectorRef)
      const childChangeDetectorRef: ChangeDetectorRef = componentRef.injector.get(
        ChangeDetectorRef
      );

      this.subscription = this.data.subscribe(newData => {
        this.setProps(instance, newData);
        childChangeDetectorRef.markForCheck();
        // Must detect changes on the current component in order to update any changes in child component's @HostBinding properties (angular/angular#22560)
        this.changeDetectorRef.detectChanges();
      });
    });
  }

  ngOnDestroy(): void {
    this.target.clear();
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  /**
   * Set inputs and outputs
   */
  private setProps(instance: any, { props = {} }: NgStory): void {
    const changes: SimpleChanges = {};
    const hasNgOnChangesHook = !!instance.ngOnChanges;

    Object.keys(props).forEach((key: string) => {
      const value = props[key];
      const instanceProperty = instance[key];

      if (!(instanceProperty instanceof EventEmitter) && (value !== undefined && value !== null)) {
        // eslint-disable-next-line no-param-reassign
        instance[key] = value;
        if (hasNgOnChangesHook) {
          const previousValue = this.previousValues[key];

          if (previousValue !== value) {
            changes[key] = new SimpleChange(previousValue, value, !this.previousValues.hasOwnProperty(key));
            this.previousValues[key] = value;
          }
        }
      } else if (typeof value === 'function' && key !== 'ngModelChange') {
        instanceProperty.subscribe(value);
      }
    });

    this.callNgOnChangesHook(instance, changes);
    this.setNgModel(instance, props);
  }

  /**
   * Manually call 'ngOnChanges' hook because angular doesn't do that for dynamic components
   * Issue: [https://github.com/angular/angular/issues/8903]
   */
  private callNgOnChangesHook(instance: any, changes: SimpleChanges): void {
    if (Object.keys(changes).length) {
      instance.ngOnChanges(changes);
    }
{'fraction_non_alphanumeric': 0.12081454147874812, 'fraction_numerical': 0.003826704933716004, 'mean_word_length': 3.522867737948084, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 15, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7589925', 'n_tokens_mistral': 2078, 'n_tokens_neox': 2039, 'n_words': 513}