简体   繁体   中英

Angular - Service injecting dynamic component?

I have a working code which injects any component via a service to the HTML:

ModalWindow.ts:

@Component({
  selector: 'modal-window'
  template: `
    <div class="modal-dialog" role="document">
        <div class="modal-content"><ng-content></ng-content></div>
    </div>
  `
})
export class ModalWindow {
}

Modalcontent.ts :

@Component({
  selector: 'modal-content'
  template: `
    I'm beeing opened as modal!
  `
})
export class ModalContent {
}

ModalService.ts :

/*1*/   @Injectable()
/*2*/   export class ModalService {
/*3*/     
/*4*/     constructor(private _appRef: ApplicationRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {
/*5*/     }
/*6*/     
/*7*/     open(content: any) {
/*8*/       const contentCmpFactory = this._cfr.resolveComponentFactory(content);
/*9*/       const windowCmpFactory = this._cfr.resolveComponentFactory(ModalWindow); 
/*10*/       
/*11*/       const contentCmpt = contentCmpFactory.create(this._injector);
/*12*/       const windowCmpt = windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);
/*13*/       
/*14*/       document.querySelector('body').appendChild(windowCmpt.location.nativeElement);
/*15*/       
/*16*/       this._appRef.attachView(contentCmpt.hostView);
/*17*/       this._appRef.attachView(windowCmpt.hostView);
/*18*/     }
/*19*/   }

App.ts:

@Component({
  selector: 'my-app',
  template: `
    <button (click)="open()">Open modal window</button>
  `,
})

Result (when click a button which calls this service method ) :

在此处输入图片说明

I already know what contentCmpFactory and windowCmpFactory are ( lines #8,9 )

But I don't udnerstnad what's going on later. Regarding lines #11,#12 - the docs says "creates a new component".

Questions :

1 - line #12 : What does [[contentCmpt.location.nativeElement]] do ? ( the docs says its type is projectableNodes?: any[][] - What do they mean ??)

2 - line #14 : What does [[windowCmpt.location.nativeElement]] do ?

3 - line #16,#17 : what and why do I need them if I already did appendChild ? ( docs says : Attaches a view so that it will be dirty checked. - so ?).

PLUNKER

Answers:

1) Angular takes ComponentFactory and create component instance with given element injector and with array of projectable nodes

windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);

1.1 Element Injector will be used when angular will resolve dependency

const value = startView.root.injector.get(depDef.token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);

Here is also simple illustration of dependency resolution algorithm for app without lazy loading. With lazy loading it will look a litte more complicated.

在此处输入图片说明

For more details see design doc element injector vs module injector

1.2 Projectable nodes are the node elements, which are "projected"(transcluded) in the ng-content that we have in the template of our component.

In order to project something our component template has to contain ng-content node.

@Component({
  selector: 'modal-window',
  template: `
    <div class="modal-dialog">
      <div class="modal-content">
        <ng-content></ng-content> // <== place for projection
      </div>
    </div>
  ` 
})
export class ModalWindow {

We can use component above in parent component template as follows:

<modal-window>
  <modal-content></modal-content>
  <div>Some other content</div>
</modal-window>

So our final result will look like:

<modal-window>
  <div class="modal-dialog">
    <div class="modal-content">
       <modal-content></modal-content> // our projectable nodes
       <div>Some other content</div>   // replaced ng-content
    </div>
  </div>
</modal-window>

So when we're passing projectable nodes to create method

windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);

we do the same things as described above.

We'are getting reference ( contentCmpt.location ) to the host element of created early contentCmpt component. This is modal-content element. And then angular will do all magic to project it in ng-content place.

In example above i added one div

<modal-window>
  <modal-content></modal-content>
  <div>Some other content</div> <== here
</modal-window>

So the real code should looks like:

let div = document.createElement('div');
div.textContent = 'Some other content';
windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement, div]]);

In conclusion Why is projectableNodes an any[][]?

2) During the next line

document.querySelector('body').appendChild(windowCmpt.location.nativeElement);

we're getting reference to created in memory modal-window element. ComponentRef allows us to do this because it stores reference to the host element in location getter

export abstract class ComponentRef<C> {
  /**
   * Location of the Host Element of this Component Instance.
   */
  abstract get location(): ElementRef;

and then inseting it in document.body tag as last child. So we see it on the page.

3) Let's say our ModalContent has not just static content but will perform some operations for interaction.

@Component({
  selector: 'modal-content',
  template: `
    I'm beeing opened as modal! {{ counter }}
    <button (click)="counter = counter + 1">Increment</button>
  `
})
export class ModalContent {
  counter = 1;
}

If we remove

 this._appRef.attachView(contentCmpt.hostView);

then our view will not being updated during change detection cycle because we created view via ComponentFactory.create and our view is not part of any item in change detection tree (unlike creation via ViewContainerRef.createComponent ). Angular opened API for such purposes and we can easily add view to root views https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L428 and after that our component will be updated during Application.tick https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L558

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM