【问题标题】:Dynamic Components - createComponent - OnChanges动态组件 - createComponent - OnChanges
【发布时间】:2018-11-03 03:39:31
【问题描述】:

我正在通过createComponent 方法创建一个动态组件,但我无法让我的child 组件更新它的input 值,它是通过parent 通过component.instance.prop = somevalue 方法传递给它的,但是,当我更新 parent 中的值时,孩子并没有更新它的引用。

父组件:

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;
  }
}

子组件:

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;
  }
}

问题:如何获得通过createComponent() 方法创建的动态child 组件,以便在parent 组件中的值发生变化时更新其值?

【问题讨论】:

    标签: angular


    【解决方案1】:

    如果您创建一个带有每次值更改时发出的 observable 的服务,并且在动态组件的 onInit 上订阅服务上的 observable,那么您从分配给它的 observable 中获得的数据会怎样?您的组件属性...我使用了类似的东西,并且似乎可以正常工作。

    这是我注入 CarouselService 的 parentComponent:

    
        @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;
            });
          }
        }
    
    

    那么我动态注入的组件是这样的:

        @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();
            }
          }
        }
    
    

    因此,当我从 parentComponent 执行函数 clickOnCross 时,我需要通过调用服务上的函数来更新动态注入组件上的 Input activeSlide,该函数更新 activeSlide 并将值发送到所有动态注入的组件。这是 CarouselService 的代码:

    
        @Injectable({
          providedIn: 'root'
        })
        export class CarouselService {
    
          currentIndex = new Subject<number>();
    
          constructor() { }
    
          updateIndex(newIndex: number) {
            this.currentIndex.next(newIndex);
          }
        }
    
    

    【讨论】:

      【解决方案2】:

      您可以在父组件中执行此操作。 Here 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);
      }
      

      【讨论】:

      • 我已经这样做了,但是每次都必须更新this.component.instance.name ref 似乎是一种可怕的方法,真的没有其他方法吗?
      • 我发现了这个问题stackoverflow.com/questions/44049225/…。并将示例更改为此stackblitz.com/edit/…。此处为子组件手动调用 ngOnChanges。
      • 是的,我想我得到的是有一种方法可以不每次都手动调用更新。我觉得应该有一种自动处理更改的方法,就像child-component [name]="name" 方法自动处理它与onChanges 一样。
      【解决方案3】:

      只需通过component.instance.name = this.name 重新分配您姓名的所有更改。为此,在每个 (keyup) 事件上实现一个处理函数:

      @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

      【讨论】:

      • 我已经这样做了,但是每次都必须更新this.component.instance.name ref 似乎是一种可怕的方法,真的没有其他方法吗?
      • 我在setter 中看到了this.component.detectChanges(),但这仍然不起作用
      猜你喜欢
      • 2020-02-27
      • 1970-01-01
      • 1970-01-01
      • 2017-07-09
      • 2016-06-06
      • 2017-05-24
      • 1970-01-01
      • 1970-01-01
      • 2017-07-05
      相关资源
      最近更新 更多