3
votes

I'm creating a dynamic component via the createComponent method, and I'm having trouble getting my child component to update it's input values passed into it from the parent via component.instance.prop = somevalue method, however when I update the value in the parent the child isn't updating it's reference.

ParentComponent:

import {
  Component,
  ViewChild,
  ViewContainerRef,
  ComponentFactoryResolver,
  AfterContentInit
} from '@angular/core';

import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'parent-component',
  template: `
    <div>
      <input type="text" (keyup)="name = $event.target.value">
      <span>{{ name }}</span>
    </div>
    <ng-container #container></ng-container>
  `,
  styles: []
})
export class ParentComponent implements AfterContentInit {
  @ViewChild('container', { read: ViewContainerRef}) container: ViewContainerRef;
  private _name = 'John Doe';
  get name() {
    return this._name;
  }
  set name(name: string) {
    this._name = name;
  }
  constructor(private resolver: ComponentFactoryResolver) { }

  ngAfterContentInit() {
    let factory = this.resolver.resolveComponentFactory(ChildComponent);
    let component = this.container.createComponent(factory);
    component.instance.name = this.name;
  }
}

ChildComponent:

import {
  Component,
  OnInit,
  Input,
  OnChanges,
  SimpleChanges
} from '@angular/core';

@Component({
  selector: 'child-component',
  template: `
    <div>
      {{ name }}
    </div>
  `,
  styles: []
})
export class ChildComponent implements OnChanges {
  _name: string;
  get name() {
    return this._name;
  }
  set name(name: string) {
    this._name = name;
  }
  constructor() { }
  ngOnChanges(changes: SimpleChanges) {
    console.log('onchanges ', changes);
    this._name = changes.name.currentValue;
  }
}

Question: How can I get a dynamic child component created via the createComponent() method to update it's value when the value changes in the parent component?

3

3 Answers

3
votes

You can do this in parent component. Here example in stackblitz.

template: `
<div>
  <input type="text" (keyup)="onKeyUp($event)">
  <span>{{ name }}</span>
</div>
<ng-container #container></ng-container>
`,

childComponent: ChildComponent;

ngAfterContentInit() {
  let factory = this.resolver.resolveComponentFactory(ChildComponent);
  let component = this.container.createComponent(factory);
  this.childComponent = component.instance;
  this.childComponent.name = this.name;
}

onKeyUp($event) {
  const changes = {
    name: new SimpleChange(this.name, $event.target.value, false)
  }

  this.name = $event.target.value;
  this.childComponent.ngOnChanges(changes);
}
0
votes

Just reassign every change on your name via component.instance.name = this.name. For this implement a handler function on every (keyup) event:

@Component({
  selector: 'parent-component',
  template: `
    <div>
      <input type="text" (keyup)="onNameChange($event)">
      <span>{{ name }}</span>
    </div>
    <ng-container #container></ng-container>
  `,
  styles: []
})
export class ParentComponent implements AfterContentInit {
  @ViewChild('container', { read: ViewContainerRef}) container: ViewContainerRef;
  private component;

  private _name = 'John Doe';
  get name() {
    return this._name;
  }
  set name(name: string) {
    this._name = name;
  }
  constructor(private resolver: ComponentFactoryResolver) { }

  onNameChange(event) {
    this.name = event.target.value;
    this.component.instance.name = this.name;
  }

  ngAfterContentInit() {
    let factory = this.resolver.resolveComponentFactory(ChildComponent);
    this.component = this.container.createComponent(factory);
    this.component.instance.name = this.name;
  }
}

Click here for StackBlitz DEMO

-1
votes

What if you create a service with an observable that emits each time a value changes and on the onInit of the dynamic component you make a subscription to the observable on the Service, then the data you get from the observable you assign it to your component property... I used something like that and seems to be working.

Here is the parentComponent where I injected the CarouselService:


    @Component({
      selector: 'app-carousel',
      templateUrl: './carousel.component.html',
      styleUrls: ['./carousel.component.scss'],
      providers: [ CarouselService]
    })
    export class CarouselComponent implements OnInit, AfterViewInit, AfterContentInit {

      @ViewChild('entryForSlides', { read: ViewContainerRef }) entryForSlides: ViewContainerRef;

      @Input() carouselSlides: Array<CarouselSlide>;
      @Input() hasPersistanceService: boolean;
      @Output() noSlidesRemaining: EventEmitter<boolean> = new EventEmitter(false);

      removedSlideToggle = false;

      carrouselInstance: any;

      activeIndex = 0;


      carouselSlideFactory: ComponentFactory<CarouselSlideComponent>;

      constructor( private _resolver: ComponentFactoryResolver, private _carouselService: CarouselService) {
        this.carouselSlideFactory = this._resolver.resolveComponentFactory(CarouselSlideComponent);
      }

      ngOnInit() { }

      ngAfterViewInit(): void {

        this.carrouselInstance = new Swiper ('.swiper-container', {
          init: false,
          // loop: true,
          spaceBetween: 30,
          // speed: 5000,
          pagination: {
            el: '.swiper-pagination',
          },
          // Navigation arrows
          navigation: {
            nextEl: '.swiper-button-nextSlide',
            prevEl: '.swiper-button-previousSlide',
          }
        });

        this.carrouselInstance.on('slideChangeTransitionEnd', () => {
          this.activeIndex = this.carrouselInstance.realIndex;
          this._carouselService.updateIndex(this.activeIndex);
        });

        this.carrouselInstance.init();
      }

      ngAfterContentInit(): void {
        this.generateSlides();
      }

      clickOnCross() {
        this._carouselService.updateIndex(this.activeIndex);
        this.entryForSlides.clear();
        this.carouselSlides.splice(this.carrouselInstance.realIndex, 1);
        this.generateSlides();

        // Timeout to update carousel with the new DOM slides (little hack while a better solution is found): DO NOT REMOVE
        setTimeout(() => {
          this.carrouselInstance.update();
        }, 1);
        if (this.carouselSlides.length <= 0 ) {
          this.noSlidesRemaining.emit();
        }
      }

      generateSlides() {
        this.carouselSlides.forEach((element, index) => {
          const component = this.entryForSlides.createComponent(this.carouselSlideFactory);
          component.instance.carouselSlide = element;
          component.instance.numberOfIndex = index;
          component.instance.activeSlide = this.activeIndex;
        });
      }
    }

then the component that I inject dynamically is this:

    @Component({
      selector: 'app-carousel-slide',
      templateUrl: './carousel-slide.component.html',
      styleUrls: ['./carousel-slide.component.scss']
    })
    export class CarouselSlideComponent implements OnInit, OnDestroy {

      @HostBinding('class.swiper-slide') public mustAddSwiperSlideClass = true;
      carouselSlide: CarouselSlide;
      numberOfIndex: number;
      activeSlide: number;
      subActiveSlide: Subscription;

      constructor(private _carouselService: CarouselService) { }

      ngOnInit() {
        this.subActiveSlide = this._carouselService.currentIndex.subscribe(
          (data: number) => {
            this.activeSlide = data;
          }
        );
      }

      ngOnDestroy() {
        if (this.subActiveSlide) {
          this.subActiveSlide.unsubscribe();
        }
      }
    }

So when I execute the function clickOnCross from parentComponent, I need to update the Input activeSlide on the dynamic injected components by calling the function on the service that updates the activeSlide and emits the value to all dynamically injected components. Here is the code for the CarouselService:


    @Injectable({
      providedIn: 'root'
    })
    export class CarouselService {

      currentIndex = new Subject<number>();

      constructor() { }

      updateIndex(newIndex: number) {
        this.currentIndex.next(newIndex);
      }
    }