कोणीय 2.0 के साथ गतिशील घटक संकलित करने के लिए मैं गतिशील टेम्पलेट का उपयोग / निर्माण कैसे कर सकता हूं?


197

मैं गतिशील रूप से एक टेम्पलेट बनाना चाहता हूं। इसका उपयोग ComponentTypeरनटाइम और स्थान बनाने के लिए किया जाना चाहिए (यहां तक ​​कि इसे होस्ट करने वाले घटक के अंदर कहीं जगह पर)

आरसी 4 तक मैं उपयोग कर रहा था ComponentResolver, लेकिन आरसी 5 के साथ मुझे निम्नलिखित संदेश मिलता है:

ComponentResolver is deprecated for dynamic compilation.
Use ComponentFactoryResolver together with @NgModule/@Component.entryComponents or ANALYZE_FOR_ENTRY_COMPONENTS provider instead.
For runtime compile only, you can also use Compiler.compileComponentSync/Async.

मुझे यह दस्तावेज़ मिला ( कोणीय 2 तुल्यकालिक गतिशील घटक निर्माण )

और समझें कि मैं भी उपयोग कर सकता हूं

  • के ngIfसाथ गतिशील की तरह ComponentFactoryResolver। अगर मैं ज्ञात घटकों को अंदर से पास करता हूं @Component({entryComponents: [comp1, comp2], ...})- तो मैं उपयोग कर सकता हूं.resolveComponentFactory(componentToRender);
  • वास्तविक क्रम संकलन, के साथ Compiler...

लेकिन सवाल यह है कि इसका उपयोग कैसे किया जाए Compiler? ऊपर नोट कहता है कि मुझे कॉल करना चाहिए: Compiler.compileComponentSync/Async- तो कैसे?

उदाहरण के लिए। मैं एक प्रकार की सेटिंग्स के लिए इस तरह के टेम्पलेट को (कुछ कॉन्फ़िगरेशन स्थितियों के आधार पर) बनाना चाहता हूं

<form>
   <string-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></string-editor>
   <string-editor
     [propertyName]="'description'"
     [entity]="entity"
   ></string-editor>
   ...

और एक अन्य मामले में यह ( string-editorसाथ प्रतिस्थापित किया जाता है text-editor)

<form>
   <text-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></text-editor>
   ...

और इतने पर ( editorsसंपत्ति प्रकारों द्वारा भिन्न संख्या / दिनांक / संदर्भ , कुछ उपयोगकर्ताओं के लिए कुछ गुण छोड़ दिए गए ...) । यानी यह एक उदाहरण है, वास्तविक विन्यास बहुत अधिक भिन्न और जटिल टेम्पलेट उत्पन्न कर सकता है।

टेम्पलेट बदल रहा है, इसलिए मैं ComponentFactoryResolverमौजूदा का उपयोग नहीं कर सकता और पास नहीं कर सकता ... मुझे इसके साथ एक समाधान की आवश्यकता है Compiler


मुझे जो समाधान मिला, वह बहुत अच्छा था मैं चाहता हूं कि हर कोई इस सवाल का जवाब मेरे जवाब पर एक नज़र डाले जो इस समय बहुत नीचे है। :)
रिचर्ड Houltz


यहाँ हर एक उत्तर के साथ समस्या है और $compileवास्तव में यह क्या कर सकता है कि ये विधियाँ नहीं कर सकते हैं - मैं एक ऐसा एप्लिकेशन बना रहा हूँ जहाँ मैं केवल HTML को संकलित करना चाहता हूँ क्योंकि यह किसी तीसरे पक्ष के पृष्ठ और ajax कॉल के माध्यम से आता है। मैं पृष्ठ से HTML नहीं निकाल सकता और इसे अपने स्वयं के टेम्पलेट में रख सकता हूं। विलाप
अग्ली गार्डनर

@AugieGardner ऐसा एक कारण है कि यह डिज़ाइन द्वारा संभव नहीं है। खराब वास्तुशिल्प निर्णयों या विरासत प्रणालियों के लिए कोणीय दोष नहीं है जो कुछ लोगों के पास है। यदि आप मौजूदा एचटीएमएल-कोड को पार्स करना चाहते हैं, तो आप एक अन्य ढांचे का उपयोग करने के लिए स्वतंत्र हैं क्योंकि कोणीय वेबकॉमर्स के साथ पूरी तरह से ठीक काम करता है। अनुभवहीन प्रोग्रामर की भीड़ को निर्देशित करने के लिए स्पष्ट सीमाएं निर्धारित करना कुछ विरासत प्रणालियों के लिए गंदे हैक की अनुमति देने से अधिक महत्वपूर्ण है।
फिल

जवाबों:


163

EDIT - 2.3.0 (2016-12-07) से संबंधित

नोट: पिछले संस्करण का समाधान पाने के लिए, इस पोस्ट के इतिहास की जाँच करें

इसी तरह के विषय पर यहां चर्चा की गई है समतुल्य $ संकलन का कोणीय 2 में । हम उपयोग करने की आवश्यकता JitCompilerहै और NgModuleNgModuleयहाँ Angular2 के बारे में अधिक पढ़ें :

संक्षेप में

नहीं है एक काम plunker / उदाहरण (गतिशील टेम्पलेट, गतिशील घटक प्रकार, गतिशील मॉड्यूल, JitCompiler... कार्रवाई में)

प्रमुख है:
1) बनाने खाका
2) को खोजने के ComponentFactoryकैश में - के लिए जाना 7)
3) - बनाने Component
4) - बनाने Module
5) - संकलन Module
6) - वापसी (और बाद में उपयोग के लिए कैश) ComponentFactory
7) उपयोग लक्ष्य और ComponentFactoryएक उदाहरण बनाने के लिए गतिशील काComponent

यहां एक कोड स्निपेट है (इसके बारे में अधिक यहां ) - हमारा कस्टम बिल्डर सिर्फ निर्मित / कैश किया गया है ComponentFactoryऔर दृश्य प्लेसहोल्डर का एक उदाहरण बनाने के लिए लक्ष्य प्लेसहोल्डर उपभोग करता हैDynamicComponent

  // here we get a TEMPLATE with dynamic content === TODO
  var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);

  // here we get Factory (just compiled or from cache)
  this.typeBuilder
      .createComponentFactory(template)
      .then((factory: ComponentFactory<IHaveDynamicData>) =>
    {
        // Target will instantiate and inject component (we'll keep reference to it)
        this.componentRef = this
            .dynamicComponentTarget
            .createComponent(factory);

        // let's inject @Inputs to component instance
        let component = this.componentRef.instance;

        component.entity = this.entity;
        //...
    });

यह वह है - संक्षेप में। अधिक विवरण प्राप्त करने के लिए .. नीचे पढ़ें

टी एल एंड डॉ

एक प्लंकर का निरीक्षण करें और कुछ स्निपेट के स्पष्टीकरण के मामले में विवरण पढ़ने के लिए वापस आएं

विस्तृत विवरण - Angular2 RC6 ++ और रनटाइम घटक

इस परिदृश्य के विवरण के नीचे , हम करेंगे

  1. एक मॉड्यूल बनाएं PartsModule:NgModule (छोटे टुकड़ों का धारक)
  2. एक और मॉड्यूल बनाएं DynamicModule:NgModule, जिसमें हमारा गतिशील घटक होगा (और PartsModuleगतिशील रूप से संदर्भ )
  3. गतिशील टेम्पलेट बनाएँ (सरल दृष्टिकोण)
  4. नया Componentप्रकार बनाएं (केवल अगर टेम्पलेट बदल गया है)
  5. नया बनाएँ RuntimeModule:NgModule। इस मॉड्यूल में पहले से बनाया गया Componentप्रकार होगा
  6. JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)पाने के लिए कॉल करेंComponentFactory
  7. DynamicComponentटारगेट प्लेसहोल्डर के - जॉब का एक उदाहरण बनाएं औरComponentFactory
  8. आवंटित @Inputsकरने के लिए नया उदाहरण (से स्विच INPUTकरने के लिए TEXTAREAसंपादन) , उपभोग@Outputs

NgModule

हमें एक NgModuleएस की जरूरत है ।

जबकि मैं एक बहुत ही सरल उदाहरण दिखाना चाहूंगा, इस मामले में, मुझे तीन मॉड्यूल की आवश्यकता होगी (वास्तव में 4 - लेकिन मैं AppModule की गिनती नहीं करता हूं) । कृपया, इसे एक साधारण स्निपेट के बजाय एक वास्तविक ठोस गतिशील घटक जनरेटर के आधार के रूप में लें।

सभी छोटे घटकों के लिए एक मॉड्यूल होगा , जैसे string-editor, text-editor ( date-editor, number-editor...)

@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      DYNAMIC_DIRECTIVES
  ],
  exports: [
      DYNAMIC_DIRECTIVES,
      CommonModule,
      FormsModule
  ]
})
export class PartsModule { }

जहां DYNAMIC_DIRECTIVESएक्स्टेंसिबल हैं और हमारे गतिशील घटक टेम्पलेट / प्रकार के लिए उपयोग किए जाने वाले सभी छोटे भागों को रखने का इरादा है। एप्लिकेशन / भागों / parts.module.ts की जाँच करें

दूसरा हमारे डायनामिक स्टफ हैंडलिंग के लिए मॉड्यूल होगा। इसमें होस्टिंग घटक और कुछ प्रदाता होंगे .. जो एकल होंगे। वहाँ हम उन्हें मानक तरीके से प्रकाशित करेंगे - के साथforRoot()

import { DynamicDetail }          from './detail.view';
import { DynamicTypeBuilder }     from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';

@NgModule({
  imports:      [ PartsModule ],
  declarations: [ DynamicDetail ],
  exports:      [ DynamicDetail],
})

export class DynamicModule {

    static forRoot()
    {
        return {
            ngModule: DynamicModule,
            providers: [ // singletons accross the whole app
              DynamicTemplateBuilder,
              DynamicTypeBuilder
            ], 
        };
    }
}

के उपयोग की जांच forRoot()मेंAppModule

अंत में, हमें एक एडहॉक, रनटाइम मॉड्यूल की आवश्यकता होगी .. लेकिन इसे बाद में DynamicTypeBuilderनौकरी के हिस्से के रूप में बनाया जाएगा ।

अगले मॉड्यूल, एप्लिकेशन मॉड्यूल, वह है जो संकलक प्रदाताओं की घोषणा करता रहता है:

...
import { COMPILER_PROVIDERS } from '@angular/compiler';    
import { AppComponent }   from './app.component';
import { DynamicModule }    from './dynamic/dynamic.module';

@NgModule({
  imports:      [ 
    BrowserModule,
    DynamicModule.forRoot() // singletons
  ],
  declarations: [ AppComponent],
  providers: [
    COMPILER_PROVIDERS // this is an app singleton declaration
  ],

पढ़ें (पढ़ें) वहाँ NgModule के बारे में बहुत कुछ :

एक टेम्पलेट बिल्डर

हमारे उदाहरण में हम इस तरह की इकाई के विस्तार की प्रक्रिया करेंगे

entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
};

templateइस प्लंकर को बनाने के लिए , हम इस सरल / भोले बिल्डर का उपयोग करते हैं।

वास्तविक समाधान, एक वास्तविक टेम्पलेट बिल्डर, वह स्थान है जहां आपका एप्लिकेशन बहुत कुछ कर सकता है

// plunker - app/dynamic/template.builder.ts
import {Injectable} from "@angular/core";

@Injectable()
export class DynamicTemplateBuilder {

    public prepareTemplate(entity: any, useTextarea: boolean){
      
      let properties = Object.keys(entity);
      let template = "<form >";
      let editorName = useTextarea 
        ? "text-editor"
        : "string-editor";
        
      properties.forEach((propertyName) =>{
        template += `
          <${editorName}
              [propertyName]="'${propertyName}'"
              [entity]="entity"
          ></${editorName}>`;
      });
  
      return template + "</form>";
    }
}

यहाँ एक चाल है - यह एक टेम्पलेट बनाता है जो ज्ञात गुणों के कुछ सेट का उपयोग करता है, जैसे entity। ऐसी संपत्ति (-एएस) को गतिशील घटक का हिस्सा होना चाहिए, जिसे हम आगे बनाएंगे।

इसे थोड़ा और आसान बनाने के लिए, हम गुणों को परिभाषित करने के लिए एक इंटरफ़ेस का उपयोग कर सकते हैं, जिसे हमारे टेम्पलेट बिल्डर उपयोग कर सकते हैं। यह हमारे गतिशील घटक प्रकार द्वारा कार्यान्वित किया जाएगा।

export interface IHaveDynamicData { 
    public entity: any;
    ...
}

एक ComponentFactoryबिल्डर

यहाँ बहुत महत्वपूर्ण बात ध्यान रखना है:

हमारे घटक प्रकार, हमारे साथ निर्माण DynamicTypeBuilder, भिन्न हो सकते हैं - लेकिन केवल इसके टेम्पलेट द्वारा (ऊपर बनाया गया) । घटकों के गुण (इनपुट, आउटपुट या कुछ संरक्षित) अभी भी समान हैं। अगर हमें अलग-अलग गुणों की आवश्यकता है, तो हमें टेम्पलेट और टाइप बिल्डर के विभिन्न संयोजन को परिभाषित करना चाहिए

इसलिए, हम अपने समाधान के मूल को छू रहे हैं। बिल्डर, 1) पैदा करेगा ComponentType2) अपने बनाने NgModule3) संकलन ComponentFactory4) कैश इसके लिए बाद में पुन: उपयोग।

एक निर्भरता हमें प्राप्त करने की आवश्यकता है:

// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
    
@Injectable()
export class DynamicTypeBuilder {

  // wee need Dynamic component builder
  constructor(
    protected compiler: JitCompiler
  ) {}

और यहाँ एक स्निपेट है ComponentFactory:

// plunker - app/dynamic/type.builder.ts
// this object is singleton - so we can use this as a cache
private _cacheOfFactories:
     {[templateKey: string]: ComponentFactory<IHaveDynamicData>} = {};
  
public createComponentFactory(template: string)
    : Promise<ComponentFactory<IHaveDynamicData>> {    
    let factory = this._cacheOfFactories[template];

    if (factory) {
        console.log("Module and Type are returned from cache")
       
        return new Promise((resolve) => {
            resolve(factory);
        });
    }
    
    // unknown template ... let's create a Type for it
    let type   = this.createNewComponent(template);
    let module = this.createComponentModule(type);
    
    return new Promise((resolve) => {
        this.compiler
            .compileModuleAndAllComponentsAsync(module)
            .then((moduleWithFactories) =>
            {
                factory = _.find(moduleWithFactories.componentFactories
                                , { componentType: type });

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });
}

ऊपर हम और बनाने कैश दोनों Componentऔर Module। क्योंकि यदि टेम्प्लेट (वास्तव में उस सभी का वास्तविक गतिशील भाग) समान है .. तो हम पुनः उपयोग कर सकते हैं

और यहां दो विधियां हैं, जो वास्तव में शांत तरीके का प्रतिनिधित्व करती हैं कि कैसे एक सजाए गए वर्गों / प्रकारों को रनटाइम बनाने के लिए। इतना ही नहीं @Componentबल्कि@NgModule

protected createNewComponent (tmpl:string) {
  @Component({
      selector: 'dynamic-component',
      template: tmpl,
  })
  class CustomDynamicComponent  implements IHaveDynamicData {
      @Input()  public entity: any;
  };
  // a component for this particular template
  return CustomDynamicComponent;
}
protected createComponentModule (componentType: any) {
  @NgModule({
    imports: [
      PartsModule, // there are 'text-editor', 'string-editor'...
    ],
    declarations: [
      componentType
    ],
  })
  class RuntimeComponentModule
  {
  }
  // a module for just this Type
  return RuntimeComponentModule;
}

जरूरी:

हमारे घटक गतिशील प्रकार भिन्न होते हैं, लेकिन सिर्फ टेम्पलेट द्वारा। तो हम उस तथ्य का उपयोग उन्हें कैश करने के लिए करते हैं। यह वास्तव में बहुत महत्वपूर्ण है। Angular2 इनको कैश भी करेगा .. टाइप करके । और अगर हम एक ही टेम्पलेट के लिए नए प्रकारों को फिर से बनाएंगे ... हम मेमोरी लीक उत्पन्न करना शुरू कर देंगे।

ComponentFactory होस्ट घटक द्वारा उपयोग किया जाता है

अंतिम टुकड़ा एक घटक है, जो हमारे गतिशील घटक के लिए लक्ष्य को होस्ट करता है, जैसे <div #dynamicContentPlaceHolder></div>। हमें इसका संदर्भ मिलता है और ComponentFactoryएक घटक बनाने के लिए उपयोग किया जाता है। यह संक्षेप में है, और यहां उस घटक के सभी टुकड़े हैं (यदि आवश्यक हो, तो यहां प्लंकर खोलें )

आइए सबसे पहले आयात विवरणों को संक्षेप में प्रस्तुत करें:

import {Component, ComponentRef,ViewChild,ViewContainerRef}   from '@angular/core';
import {AfterViewInit,OnInit,OnDestroy,OnChanges,SimpleChange} from '@angular/core';

import { IHaveDynamicData, DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder }               from './template.builder';

@Component({
  selector: 'dynamic-detail',
  template: `
<div>
  check/uncheck to use INPUT vs TEXTAREA:
  <input type="checkbox" #val (click)="refreshContent(val.checked)" /><hr />
  <div #dynamicContentPlaceHolder></div>  <hr />
  entity: <pre>{{entity | json}}</pre>
</div>
`,
})
export class DynamicDetail implements AfterViewInit, OnChanges, OnDestroy, OnInit
{ 
    // wee need Dynamic component builder
    constructor(
        protected typeBuilder: DynamicTypeBuilder,
        protected templateBuilder: DynamicTemplateBuilder
    ) {}
    ...

हम बस, टेम्पलेट और घटक बिल्डरों को प्राप्त करते हैं। अगले गुण हैं जो हमारे उदाहरण के लिए आवश्यक हैं (टिप्पणियों में अधिक)

// reference for a <div> with #dynamicContentPlaceHolder
@ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef}) 
protected dynamicComponentTarget: ViewContainerRef;
// this will be reference to dynamic content - to be able to destroy it
protected componentRef: ComponentRef<IHaveDynamicData>;

// until ngAfterViewInit, we cannot start (firstly) to process dynamic stuff
protected wasViewInitialized = false;

// example entity ... to be recieved from other app parts
// this is kind of candiate for @Input
protected entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
  };

इस सरल परिदृश्य में, हमारे होस्टिंग घटक में कोई भी नहीं है @Input। इसलिए इसे परिवर्तनों पर प्रतिक्रिया करने की आवश्यकता नहीं है। लेकिन इस तथ्य के बावजूद (और आने वाले परिवर्तनों के लिए तैयार होने के लिए) - हमें कुछ ध्वज को पेश करने की आवश्यकता है यदि घटक पहले से (पहले) शुरू किया गया था। और तभी हम जादू शुरू कर सकते हैं।

अंत में हम अपने घटक बिल्डर का उपयोग करेंगे, और इसके संकलित / कैश्ड का उपयोग करेंगे ComponentFacotry। हमारा लक्ष्य प्लेसहोल्डर का दृष्टांत के लिए कहा जाएगा कि कारखाने के साथ।Component

protected refreshContent(useTextarea: boolean = false){
  
  if (this.componentRef) {
      this.componentRef.destroy();
  }
  
  // here we get a TEMPLATE with dynamic content === TODO
  var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);

  // here we get Factory (just compiled or from cache)
  this.typeBuilder
      .createComponentFactory(template)
      .then((factory: ComponentFactory<IHaveDynamicData>) =>
    {
        // Target will instantiate and inject component (we'll keep reference to it)
        this.componentRef = this
            .dynamicComponentTarget
            .createComponent(factory);

        // let's inject @Inputs to component instance
        let component = this.componentRef.instance;

        component.entity = this.entity;
        //...
    });
}

छोटा विस्तार

इसके अलावा, हमें संकलित टेम्पलेट का एक संदर्भ रखने की आवश्यकता है .. destroy()इसे ठीक से सक्षम करने के लिए , जब भी हम इसे बदलेंगे।

// this is the best moment where to start to process dynamic stuff
public ngAfterViewInit(): void
{
    this.wasViewInitialized = true;
    this.refreshContent();
}
// wasViewInitialized is an IMPORTANT switch 
// when this component would have its own changing @Input()
// - then we have to wait till view is intialized - first OnChange is too soon
public ngOnChanges(changes: {[key: string]: SimpleChange}): void
{
    if (this.wasViewInitialized) {
        return;
    }
    this.refreshContent();
}

public ngOnDestroy(){
  if (this.componentRef) {
      this.componentRef.destroy();
      this.componentRef = null;
  }
}

किया हुआ

बिल्कुल यही बात है। जो कुछ भी गतिशील रूप से बनाया गया था उसे नष्ट करने के लिए मत भूलना (ngOnDestroy) । इसके अलावा, डायनामिक कैश करना सुनिश्चित करें typesऔर modulesयदि एकमात्र अंतर उनका टेम्पलेट है।

यह सब कार्रवाई में यहाँ की जाँच करें

इस पोस्ट के पिछले संस्करणों (जैसे RC5 संबंधित) को देखने के लिए, इतिहास की जाँच करें


50
इस तरह के एक जटिल समाधान की तरह लग रहा है, पदावनत एक बहुत ही सरल और स्पष्ट था, क्या ऐसा करने का कोई अन्य तरीका है?
तिब्बत

3
मुझे लगता है कि @tibbus जैसा ही तरीका है: इस तरह से अधिक जटिल हो गया क्योंकि यह पदावनत कोड के साथ हुआ करता था। आपके उत्तर के लिए धन्यवाद, यद्यपि।
लुसियो मोलीडीनो

5
@ribsies आपके नोट के लिए धन्यवाद। मुझे कुछ स्पष्ट करने दो। कई अन्य उत्तर इसे सरल बनाने की कोशिश करते हैं । लेकिन मैं इसे समझाने की कोशिश कर रहा हूं और इसे एक परिदृश्य में दिखा रहा हूं, वास्तविक उपयोग के करीब । हमें सामान को कैश करने की आवश्यकता होगी, हमें री-क्रिएशन इत्यादि को नष्ट करना होगा, इसलिए, जबकि गतिशील बिल्डिंग का जादू वास्तव में है type.builder.tsजैसा कि आपने बताया है, काश, कोई भी उपयोगकर्ता समझ जाता कि कैसे सभी को इसमें जगह देना है संदर्भ ... आशा है कि यह उपयोगी हो सकता है;)
रेडिम कॉहलर

7
@ रादिम कोहलर - मैंने इस उदाहरण की कोशिश की है। यह एओटी के बिना काम कर रहा है। लेकिन जब मैंने एओटी के साथ इसे चलाने की कोशिश की, तो यह "नो एनमॉड्यूल मेटाडेटा रंटाइमकंपोनेंटमॉड्यूल के लिए मिला" त्रुटि दिखाता है। क्या आप इस त्रुटि को हल करने में मेरी मदद कर सकते हैं।
तृषा

4
जवाब ही सही! लेकिन वास्तविक जीवन के अनुप्रयोगों के लिए व्यावहारिक नहीं है। कोणीय टीम को ढांचे में इसके लिए एक समाधान प्रदान करना चाहिए, क्योंकि व्यावसायिक अनुप्रयोगों में यह सामान्य आवश्यकता है। यदि नहीं, तो यह पूछा जाना चाहिए कि क्या कोणीय 2 व्यावसायिक अनुप्रयोगों के लिए सही मंच है।
कार्ल

58

EDIT (26/08/2017) : नीचे दिए गए समाधान Angular2 के साथ अच्छी तरह से काम करता है और 4. मैंने इसे एक टेम्प्लेट चर को शामिल करने और हैंडलर पर क्लिक करने के लिए अद्यतन किया है और इसे Angular 4.3 के साथ परीक्षण किया है।
Angular4 के लिए, OComir के उत्तर में वर्णित ngComponentOutlet एक बेहतर समाधान है। लेकिन अभी यह इनपुट और आउटपुट का समर्थन नहीं करता है। यदि [यह PR] ( https://github.com/angular/angular/pull/15362] स्वीकार किया जाता है, तो यह संभव होगा कि घटक घटना के माध्यम से
निर्मित ईवेंट द्वारा लौट आए। एनजी-डायनेमिक-कंपोनेंट सबसे अच्छा और सरल हो सकता है। समाधान पूरी तरह से, लेकिन मैंने अभी तक परीक्षण नहीं किया है।

@ लोंग फील्ड का जवाब हाजिर है! यहाँ एक और (तुल्यकालिक) उदाहरण है:

import {Compiler, Component, NgModule, OnInit, ViewChild,
  ViewContainerRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'my-app',
  template: `<h1>Dynamic template:</h1>
             <div #container></div>`
})
export class App implements OnInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  constructor(private compiler: Compiler) {}

  ngOnInit() {
    this.addComponent(
      `<h4 (click)="increaseCounter()">
        Click to increase: {{counter}}
      `enter code here` </h4>`,
      {
        counter: 1,
        increaseCounter: function () {
          this.counter++;
        }
      }
    );
  }

  private addComponent(template: string, properties?: any = {}) {
    @Component({template})
    class TemplateComponent {}

    @NgModule({declarations: [TemplateComponent]})
    class TemplateModule {}

    const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
    const factory = mod.componentFactories.find((comp) =>
      comp.componentType === TemplateComponent
    );
    const component = this.container.createComponent(factory);
    Object.assign(component.instance, properties);
    // If properties are changed at a later stage, the change detection
    // may need to be triggered manually:
    // component.changeDetectorRef.detectChanges();
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

पर लाइव http://plnkr.co/edit/fdP9Oc


3
मैं कहेंगे, कि यह एक उदाहरण है कि कैसे लिखना है संभव के रूप में कम कोड के रूप में करने के लिए मेरा उत्तर में के रूप में ही stackoverflow.com/a/38888009/1679310 । जब स्थिति बदलती है , तो यह उपयोगी-मामला (ज्यादातर आरई-जनरेटिंग टेम्प्लेट) होना चाहिए ... सरल ngAfterViewInitकॉल एक const templateकाम नहीं करेगा। लेकिन अगर आपका काम उपरोक्त वर्णित दृष्टिकोण को कम करना था (टेम्पलेट बनाएं, घटक बनाएं, मॉड्यूल बनाएं, इसे संकलित करें, कारखाना बनाएं .. उदाहरण बनाएं) ... आपने शायद यह किया था
रेडिम कोहलर

समाधान के लिए धन्यवाद: मुझे टेम्प्लेट यूआरएल और शैलियों को लोड करने में परेशानी हो रही है, हालांकि, मुझे निम्न त्रुटि मिलती है: कोई रिसोर्सलोडर कार्यान्वयन प्रदान नहीं किया गया है। Url लोकलहोस्ट को नहीं पढ़ सकते हैं : 3000 / app / पेज / Pages_common.css , कोई भी विचार जो मुझे याद आ रहा है?
गेरार्डलामो

क्या यह ग्रिड की तरह नियंत्रण के लिए डेटा के साथ HTML टेम्पलेट को संकलित करना संभव है? plnkr.co/edit/vJHUCnsJB7cwNJr2cCwp?p=preview इस प्लंकर में, मैं अंतिम कॉलम में छवि को कैसे संकलित और दिखा सकता हूं? कोई मदद।?
कार्तिक

1
@monnef, आप सही कह रहे हैं। मैंने कंसोल लॉग की जाँच नहीं की। मैंने कोड को ngAfterViewInit हुक के बजाय ngOnInit में घटक जोड़ने के लिए समायोजित किया है, क्योंकि पहले परिवर्तन का पता लगाने के बाद और बाद में चालू हो गया है। (देखें github.com/angular/angular/issues/10131 और इसी तरह के धागे।)
रेने हैमबर्गर

1
साफ और सरल। देव में ब्राउज़र पर सेवा करते समय अपेक्षित रूप से काम किया। लेकिन क्या यह एओटी के साथ काम करता है? जब संकलन के बाद ऐप को PROD में चलाया जाता है, तो मुझे उस समय एक "त्रुटि: रनटाइम कंपाइलर लोड नहीं होता है" घटक संकलन का प्रयास किया जाता है। (btw, मैं Ionic 3.5 का उपयोग कर रहा हूँ)
mymo

52

मैं देर से पार्टी में आया हूँ, यहाँ कोई भी समाधान मुझे मददगार नहीं लगा - बहुत गन्दा और बहुत अधिक हलका सा लगा।

मैंने जो किया, वह उपयोग कर रहा है Angular 4.0.0-beta.6 के ngComponentOutlet

इससे मुझे डायनेमिक कंपोनेंट की फाइल में लिखा गया सबसे छोटा, सरल उपाय मिल गया।

  • यहाँ एक सरल उदाहरण है जो केवल पाठ प्राप्त करता है और इसे एक टेम्पलेट में रखता है, लेकिन जाहिर है कि आप अपनी आवश्यकता के अनुसार बदल सकते हैं:
import {
  Component, OnInit, Input, NgModule, NgModuleFactory, Compiler
} from '@angular/core';

@Component({
  selector: 'my-component',
  template: `<ng-container *ngComponentOutlet="dynamicComponent;
                            ngModuleFactory: dynamicModule;"></ng-container>`,
  styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
  dynamicComponent;
  dynamicModule: NgModuleFactory<any>;

  @Input()
  text: string;

  constructor(private compiler: Compiler) {
  }

  ngOnInit() {
    this.dynamicComponent = this.createNewComponent(this.text);
    this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
  }

  protected createComponentModule (componentType: any) {
    @NgModule({
      imports: [],
      declarations: [
        componentType
      ],
      entryComponents: [componentType]
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
  }

  protected createNewComponent (text:string) {
    let template = `dynamically created template with text: ${text}`;

    @Component({
      selector: 'dynamic-component',
      template: template
    })
    class DynamicComponent implements OnInit{
       text: any;

       ngOnInit() {
       this.text = text;
       }
    }
    return DynamicComponent;
  }
}
  • संक्षिप्त विवरण:
    1. my-component - वह घटक जिसमें एक गतिशील घटक प्रतिपादन कर रहा है
    2. DynamicComponent - घटक को गतिशील रूप से बनाया जाना है और यह मेरे घटक के अंदर प्रदान कर रहा है

सभी कोणीय पुस्तकालयों को ^ एंगुलर 4.0.0 में अपग्रेड करना न भूलें

उम्मीद है कि इससे मदद करेगी, सुसंयोग!

अपडेट करें

कोणीय 5 के लिए भी काम करता है।


3
यह मेरे लिए Angular4 के साथ बहुत अच्छा काम किया। एकमात्र समायोजन जो मुझे करना था, वह गतिशील रूप से बनाए गए RuntimeComponentModule के लिए आयात मॉड्यूल निर्दिष्ट करने में सक्षम होना था।
राहुल पटेल

8
यहाँ एक त्वरित उदाहरण कोणीय क्विकार्टार्ट
राहुल पटेल

5
क्या यह समाधान "एनजी बिल्ड --प्रोड" के साथ काम करता है? ऐसा लगता है कि संकलक वर्ग और एओटी एक साथ एटीएम में फिट नहीं होते हैं।
पियरे चावरोचे

2
@OphirStern मैंने यह भी पता लगाया कि दृष्टिकोण कोणीय 5 में अच्छी तरह से काम करता है, लेकिन -प्रोड बिल्ड फ्लैग के साथ नहीं।
TaeKwonJoe

2
मैंने JitCompilerFactory का उपयोग करके कोणीय 5 (5.2.8) के साथ इसका परीक्षण किया और -प्रोड ध्वज का उपयोग करने से काम नहीं होता है! क्या किसी के पास एक समाधान है? (BTW JitCompilerFactory --prod ध्वज के बिना निर्दोष रूप से काम करता है)
फ्रैंक

20

2019 जून उत्तर

बढ़िया खबर! ऐसा लगता है कि @ कोणीय / सीडीके पैकेज में अब पोर्टल्स के लिए प्रथम श्रेणी का समर्थन है !

लेखन के समय तक, मुझे उपरोक्त आधिकारिक डॉक्स विशेष रूप से मददगार नहीं लगे (विशेषकर डायनेमिक डेटा भेजने और डायनेमिक से घटनाओं को प्राप्त करने के संबंध में)। सारांश में, आपको निम्न की आवश्यकता होगी:

चरण 1) अपना अद्यतन करें AppModule

पैकेज PortalModuleसे आयात करें @angular/cdk/portalऔर अपने डायनामिक कंपोनेंट को अंदर रजिस्टर करेंentryComponents

@NgModule({
  declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
  imports:      [ ..., PortalModule, ... ],
  entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }

चरण 2. विकल्प ए: यदि आपको अपने गतिशील घटकों से डेटा पास करने और ईवेंट प्राप्त करने की आवश्यकता नहीं है :

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add child component</button>
    <ng-template [cdkPortalOutlet]="myPortal"></ng-template>
  `
})
export class AppComponent  {
  myPortal: ComponentPortal<any>;
  onClickAddChild() {
    this.myPortal = new ComponentPortal(MyDynamicComponent);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child.</p>`
})
export class MyDynamicComponent{
}

इसे एक्शन में देखें

चरण 2. विकल्प बी: यदि आपको अपने गतिशील घटकों से डेटा पास करने और ईवेंट प्राप्त करने की आवश्यकता है :

// A bit of boilerplate here. Recommend putting this function in a utils 
// file in order to keep your component code a little cleaner.
function createDomPortalHost(elRef: ElementRef, injector: Injector) {
  return new DomPortalHost(
    elRef.nativeElement,
    injector.get(ComponentFactoryResolver),
    injector.get(ApplicationRef),
    injector
  );
}

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add random child component</button>
    <div #portalHost></div>
  `
})
export class AppComponent {

  portalHost: DomPortalHost;
  @ViewChild('portalHost') elRef: ElementRef;

  constructor(readonly injector: Injector) {
  }

  ngOnInit() {
    this.portalHost = createDomPortalHost(this.elRef, this.injector);
  }

  onClickAddChild() {
    const myPortal = new ComponentPortal(MyDynamicComponent);
    const componentRef = this.portalHost.attach(myPortal);
    setTimeout(() => componentRef.instance.myInput 
      = '> This is data passed from AppComponent <', 1000);
    // ... if we had an output called 'myOutput' in a child component, 
    // this is how we would receive events...
    // this.componentRef.instance.myOutput.subscribe(() => ...);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child. <strong>{{myInput}}</strong></p>`
})
export class MyDynamicComponent {
  @Input() myInput = '';
}

इसे एक्शन में देखें


1
यार, तुम बस nailed। इस पर ध्यान जाएगा। मैं विश्वास नहीं कर सकता था कि जब तक मुझे एक करने की आवश्यकता नहीं होती, तब तक कोणीय में एक सरल गतिशील घटक को जोड़ना कितना मुश्किल है। यह रीसेट करने जैसा है और पूर्व-JQuery समय पर वापस जाएं।
Gi1ber7

2
@ Gi1ber7 क्या मुझे सही पता है? यह उन्हें इतना लंबा क्यों लगा?
स्टीफन पॉल

1
अच्छा तरीका है, लेकिन क्या आप जानते हैं कि चाइल्डकंपोनेंट को पैरामीटर कैसे पास करना है?
स्नुक

1
@ इसने आपके प्रश्न का जवाब दिया हो सकता है stackoverflow.com/questions/47469844/…
स्टीफन पॉल

4
@StephenPaul यह कैसे करता है Portalदृष्टिकोण से अलग ngTemplateOutletऔर ngComponentOutlet? Mo
ग्लेन मोहम्मद

18

मैंने एक फाइल में जो कुछ भी सीखा, उसे कॉम्पैक्ट करने का फैसला किया । RC5 से पहले की तुलना में यहाँ विशेष रूप से लेने के लिए बहुत कुछ है। ध्यान दें कि इस स्रोत फ़ाइल में AppModule और AppComponent शामिल हैं।

import {
  Component, Input, ReflectiveInjector, ViewContainerRef, Compiler, NgModule, ModuleWithComponentFactories,
  OnInit, ViewChild
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

@Component({
  selector: 'app-dynamic',
  template: '<h4>Dynamic Components</h4><br>'
})
export class DynamicComponentRenderer implements OnInit {

  factory: ModuleWithComponentFactories<DynamicModule>;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }

  ngOnInit() {
    if (!this.factory) {
      const dynamicComponents = {
        sayName1: {comp: SayNameComponent, inputs: {name: 'Andrew Wiles'}},
        sayAge1: {comp: SayAgeComponent, inputs: {age: 30}},
        sayName2: {comp: SayNameComponent, inputs: {name: 'Richard Taylor'}},
        sayAge2: {comp: SayAgeComponent, inputs: {age: 25}}};
      this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
        .then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
          this.factory = moduleWithComponentFactories;
          Object.keys(dynamicComponents).forEach(k => {
            this.add(dynamicComponents[k]);
          })
        });
    }
  }

  addNewName(value: string) {
    this.add({comp: SayNameComponent, inputs: {name: value}})
  }

  addNewAge(value: number) {
    this.add({comp: SayAgeComponent, inputs: {age: value}})
  }

  add(comp: any) {
    const compFactory = this.factory.componentFactories.find(x => x.componentType === comp.comp);
    // If we don't want to hold a reference to the component type, we can also say: const compFactory = this.factory.componentFactories.find(x => x.selector === 'my-component-selector');
    const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
    const cmpRef = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
    Object.keys(comp.inputs).forEach(i => cmpRef.instance[i] = comp.inputs[i]);
  }
}

@Component({
  selector: 'app-age',
  template: '<div>My age is {{age}}!</div>'
})
class SayAgeComponent {
  @Input() public age: number;
};

@Component({
  selector: 'app-name',
  template: '<div>My name is {{name}}!</div>'
})
class SayNameComponent {
  @Input() public name: string;
};

@NgModule({
  imports: [BrowserModule],
  declarations: [SayAgeComponent, SayNameComponent]
})
class DynamicModule {}

@Component({
  selector: 'app-root',
  template: `
        <h3>{{message}}</h3>
        <app-dynamic #ad></app-dynamic>
        <br>
        <input #name type="text" placeholder="name">
        <button (click)="ad.addNewName(name.value)">Add Name</button>
        <br>
        <input #age type="number" placeholder="age">
        <button (click)="ad.addNewAge(age.value)">Add Age</button>
    `,
})
export class AppComponent {
  message = 'this is app component';
  @ViewChild(DynamicComponentRenderer) dcr;

}

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent, DynamicComponentRenderer],
  bootstrap: [AppComponent]
})
export class AppModule {}`

10

मेरे पास यह दिखाने के लिए एक सरल उदाहरण है कि कोणीय 2 आरसी 6 गतिशील घटक कैसे करें।

कहते हैं, आपके पास एक डायनामिक HTML टेम्प्लेट = टेम्प्लेट 1 है और डायनेमिक लोड करना चाहते हैं, सबसे पहले कंपोनेंट में लपेटें

@Component({template: template1})
class DynamicComponent {}

यहाँ html के रूप में template1, ng2 घटक हो सकता है

Rc6 से, इस घटक को @NgModule लपेटना होगा। @NgModule, anglarJS 1 में मॉड्यूल की तरह, यह ng2 अनुप्रयोग के विभिन्न भाग को अलग करता है, इसलिए:

@Component({
  template: template1,

})
class DynamicComponent {

}
@NgModule({
  imports: [BrowserModule,RouterModule],
  declarations: [DynamicComponent]
})
class DynamicModule { }

(यहाँ RouterModule का आयात करें मेरे उदाहरण में मेरे html में कुछ मार्ग घटक हैं जैसा कि आप बाद में देख सकते हैं)

अब आप डायनामिक मोड को संकलित कर सकते हैं: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

और हमें इसे लोड करने के लिए app.moudule.ts में ऊपर रखना होगा, कृपया मेरे app.moudle.ts को देखें। अधिक और पूर्ण विवरणों के लिए देखें: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts और app.moudle.ts

और डेमो देखें: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview


3
तो, आपने मॉड्यूल 1, मॉड्यूल 2, मॉड्यूल 3 घोषित किया है। और अगर आपको एक और "डायनामिक" टेम्प्लेट सामग्री की आवश्यकता होगी, तो आपको एक रद्दीकरण (फ़ाइल) फॉर्म moudle4 (मॉड्यूल 4.ts) बनाने की आवश्यकता होगी, है ना? यदि हाँ, तो यह गतिशील नहीं लगता है। यह स्थिर है, है ना? या मुझे कुछ याद है?
रेडिम कोहलर

ऊपर "टेम्पलेट 1" html का स्ट्रिंग है, आप इसमें कुछ भी डाल सकते हैं और हम इस डायनामिक टेम्पलेट को कॉल करते हैं, क्योंकि यह प्रश्न पूछ रहा है
लॉन्ग फील्ड

6

कोणीय 7.x में मैंने इसके लिए कोणीय-तत्वों का उपयोग किया।

  1. @ कोणीय-तत्व npm i @ कोणीय / तत्व -s स्थापित करें

  2. गौण सेवा बनाएँ।

import { Injectable, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { IStringAnyMap } from 'src/app/core/models';
import { AppUserIconComponent } from 'src/app/shared';

const COMPONENTS = {
  'user-icon': AppUserIconComponent
};

@Injectable({
  providedIn: 'root'
})
export class DynamicComponentsService {
  constructor(private injector: Injector) {

  }

  public register(): void {
    Object.entries(COMPONENTS).forEach(([key, component]: [string, any]) => {
      const CustomElement = createCustomElement(component, { injector: this.injector });
      customElements.define(key, CustomElement);
    });
  }

  public create(tagName: string, data: IStringAnyMap = {}): HTMLElement {
    const customEl = document.createElement(tagName);

    Object.entries(data).forEach(([key, value]: [string, any]) => {
      customEl[key] = value;
    });

    return customEl;
  }
}

ध्यान दें कि आप कस्टम एलिमेंट टैग कोणीय घटक चयनकर्ता के साथ अलग होना चाहिए। AppUserIconComponent में:

...
selector: app-user-icon
...

और इस मामले में कस्टम टैग नाम मैंने "उपयोगकर्ता-आइकन" का उपयोग किया।

  1. फिर आपको AppComponent में रजिस्टर कॉल करना होगा:
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. और अब आपके कोड के किसी भी स्थान पर आप इसे इस तरह से उपयोग कर सकते हैं:
dynamicComponents.create('user-icon', {user:{...}});

या इस तरह:

const html = `<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>`;

this.content = this.domSanitizer.bypassSecurityTrustHtml(html);

(टेम्प्लेट में):

<div class="comment-item d-flex" [innerHTML]="content"></div>

ध्यान दें कि दूसरे मामले में आपको JSON.stringify के साथ ऑब्जेक्ट पास करना होगा और उसके बाद इसे फिर से पार्स करना होगा। मुझे बेहतर समाधान नहीं मिल रहा है।


दृष्टिकोण interresting, लेकिन आप es2015 लक्षित करने के लिए (ताकि IE11 लिए कोई समर्थन नहीं) अपने tsconfig.json में, othewise यह पर विफल रही होगी की आवश्यकता होगीdocument.createElement(tagName);
Snook

नमस्ते, जैसा कि आपने इनपुट को संभालने का एक तरीका बताया है, इसलिए बाल घटकों के आउटपुट को इस तरह से भी संभाला जा सकता है?
मुस्तहसन

5

यह एनजील 2 अंतिम संस्करण में एनको -डायनेमिक से डायनामिककंपोनेंट निर्देश का उपयोग करके हल किया गया ।

उपयोग:

<div *dynamicComponent="template; context: {text: text};"></div>

जहां टेम्प्लेट आपका डायनामिक टेम्प्लेट है और संदर्भ किसी डायनामिक डेटामॉडल पर सेट किया जा सकता है, जिसे आप अपने टेम्प्लेट को बांधना चाहते हैं।


एओटी के साथ कोणीय 5 लिखने के समय यह समर्थन नहीं करता है क्योंकि जेआईटी संकलक बंडल में शामिल नहीं है।
एओटी के

क्या यह अभी भी कोणीय 7+ पर लागू होता है?
कार्लोस ई

4

मैं रेडिम द्वारा इस बहुत ही उत्कृष्ट पोस्ट के शीर्ष पर कुछ विवरण जोड़ना चाहता हूं।

मैंने इस समाधान को लिया और उस पर थोड़ा काम किया और जल्दी से कुछ सीमाओं में चला गया। मैं बस उन लोगों की रूपरेखा तैयार करूंगा और फिर समाधान भी दूंगा।

  • सबसे पहले मैं डायनामिक-डिटेल के अंदर डायनामिक-डिटेल को रेंडर करने में असमर्थ था (मूल रूप से एक दूसरे के अंदर गतिशील यूआई)।
  • अगला मुद्दा यह था कि मैं उन हिस्सों में से एक के अंदर एक डायनामिक-डिटेल प्रस्तुत करना चाहता था जिसे समाधान में उपलब्ध कराया गया था। यह प्रारंभिक समाधान के साथ भी संभव नहीं था।
  • अंत में स्ट्रिंग-एडिटर जैसे डायनामिक हिस्सों पर टेम्प्लेट यूआरएल का उपयोग करना संभव नहीं था।

मैंने इस पोस्ट के आधार पर एक और प्रश्न किया कि इन सीमाओं को कैसे प्राप्त किया जाए, जो यहाँ मिल सकती है:

कोणीय 2 में पुनरावर्ती गतिशील टेम्पलेट संकलन

मैं इन सीमाओं के जवाबों की रूपरेखा तैयार करूंगा, क्या आपको मुझे उसी मुद्दे पर चलना चाहिए, जैसा कि समाधान को और अधिक लचीला बनाता है। प्रारंभिक प्लंकर को उसी के साथ अद्यतन किया जाना भयानक होगा।

एक दूसरे के अंदर गतिशील-विस्तार घोंसला बनाने से सक्षम करने के लिए आप में आयात बयान में DynamicModule.forRoot () जोड़ने की आवश्यकता होगी type.builder.ts

protected createComponentModule (componentType: any) {
    @NgModule({
    imports: [
        PartsModule, 
        DynamicModule.forRoot() //this line here
    ],
    declarations: [
        componentType
    ],
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
}

इसके अलावा इसका उपयोग करना संभव नहीं था <dynamic-detail> स्ट्रिंग-एडिटर या टेक्स्ट-एडिटर होने के एक हिस्से के अंदर ।

यह सक्षम करने के लिए कि आपको बदलना होगा parts.module.tsऔरdynamic.module.ts

अंदर parts.module.tsआपको अंदर जोड़ना DynamicDetailहोगाDYNAMIC_DIRECTIVES

export const DYNAMIC_DIRECTIVES = [
   forwardRef(() => StringEditor),
   forwardRef(() => TextEditor),
   DynamicDetail
];

इसके अलावा, dynamic.module.tsआपको डायनेमिकडेटेल को निकालना होगा क्योंकि वे अब भागों का हिस्सा हैं

@NgModule({
   imports:      [ PartsModule ],
   exports:      [ PartsModule],
})

एक संशोधित संशोधित प्लंकर यहां पाया जा सकता है: http://plnkr.co/edit/UYnQHF?p=hi (मैंने इस मुद्दे को हल नहीं किया, मैं सिर्फ दूत हूं :-D)

अंत में गतिशील घटकों पर बनाए गए भागों में टेम्प्लाटर का उपयोग करना संभव नहीं था। एक समाधान (या वर्कअराउंड। मुझे यकीन नहीं है कि यह कोणीय बग या ढांचे का गलत उपयोग है) इसे इंजेक्ट करने के बजाय कंस्ट्रक्टर में एक कंपाइलर बनाना था।

    private _compiler;

    constructor(protected compiler: RuntimeCompiler) {
        const compilerFactory : CompilerFactory =
        platformBrowserDynamic().injector.get(CompilerFactory);
        this._compiler = compilerFactory.createCompiler([]);
    }

फिर _compilerसंकलन करने के लिए उपयोग करें , फिर टेम्पलेट यूआरएल भी सक्षम हैं।

return new Promise((resolve) => {
        this._compiler
            .compileModuleAndAllComponentsAsync(module)
            .then((moduleWithFactories) =>
            {
                let _ = window["_"];
                factory = _.find(moduleWithFactories.componentFactories, { componentType: type });

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });

मनाइए कि यह किसी और के लिए सहायक हो!

सादर मोर्टन


4

रेडमिन के उत्कृष्ट उत्तर के बाद, एंगुलर-क्ली संस्करण 1.0.0-Beta.22 और इसके बाद के संस्करण का उपयोग करने वाले सभी के लिए थोड़ा ट्वीक की आवश्यकता है।

COMPILER_PROVIDERSअब आयात नहीं किया जा सकता है (विवरण के लिए कोणीय-क्लिट गिटहब देखें )।

वैकल्पिक हल तो वहाँ है उपयोग करने के लिए नहीं COMPILER_PROVIDERSहै और JitCompilerमें providersसब पर अनुभाग, लेकिन उपयोग JitCompilerFactoryसे '@ कोणीय / संकलक' के बजाय प्रकार बिल्डर वर्ग के अंदर इस तरह:

private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();

जैसा कि आप देख सकते हैं, यह इंजेक्शन नहीं है और इस प्रकार डीआई के साथ कोई निर्भरता नहीं है। यह समाधान कोणीय-क्ली का उपयोग नहीं करने वाली परियोजनाओं के लिए भी काम करना चाहिए।


1
इस सुझाव के लिए धन्यवाद, हालांकि, मैं "डायनेमिक एचटीडीएमड्यूल" के लिए "नो एनजीमॉड्यूल मेटाडेटा" में चल रहा हूं। मेरा कार्यान्वयन stackoverflow.com/questions/40060498/…
Cybey

2
किसी के पास AOT नमूने के साथ JitCompiletFactory काम कर रहा है? मेरे पास @Cybey जैसी ही त्रुटि है
user2771738

यह वास्तव में संभव नहीं लगता है। कृपया github.com/angular/angular/issues/11780 , medium.com/@isaacplmann/… और stackoverflow.com/questions/42537138/… पर देखें
सेबस्टियन

2

मैं खुद यह देखने की कोशिश कर रहा हूं कि मैं RC4 को RC5 में कैसे अपडेट कर सकता हूं और इस तरह मैं इस प्रविष्टि पर ठोकर खाई और गतिशील घटक निर्माण के लिए नया दृष्टिकोण अभी भी मेरे लिए थोड़ा रहस्य रखता है, इसलिए मैं घटक कारखाना रिज़ॉल्वर पर कुछ भी सुझाव नहीं देता।

लेकिन, मैं जो सुझाव दे सकता हूं वह इस परिदृश्य पर घटक निर्माण के लिए थोड़ा स्पष्ट दृष्टिकोण है - बस टेम्पलेट में स्विच का उपयोग करें जो कुछ स्थिति के अनुसार स्ट्रिंग संपादक या पाठ संपादक बनाएगा:

<form [ngSwitch]="useTextarea">
    <string-editor *ngSwitchCase="false" propertyName="'code'" 
                 [entity]="entity"></string-editor>
    <text-editor *ngSwitchCase="true" propertyName="'code'" 
                 [entity]="entity"></text-editor>
</form>

और वैसे, "[" में [प्रोप] अभिव्यक्ति का एक अर्थ है, यह एक तरह से डेटा बाइंडिंग को इंगित करता है, इसलिए यदि आप जानते हैं कि आपको वैरिएबल में संपत्ति को बाँधने की आवश्यकता नहीं है, तो आप भी इसे छोड़ सकते हैं।


1
यह जाने का एक तरीका होगा .. यदि switch/ caseमें कुछ निर्णय हों। लेकिन कल्पना करें कि उत्पन्न टेम्पलेट वास्तव में बड़ा हो सकता है ... और प्रत्येक इकाई के लिए अलग-अलग, सुरक्षा द्वारा भिन्न, इकाई की स्थिति से भिन्न, प्रत्येक संपत्ति प्रकार (संख्या, दिनांक, संदर्भ ... संपादकों) द्वारा ... ऐसे मामले में, HTML टेम्पलेट में इसे हल करने से ngSwitchएक बड़ी, बहुत बड़ी htmlफ़ाइल बन जाएगी।
रेडिम कोहलर

ओह मैं आपसे सहमत हूँ। मेरे पास इस तरह का परिदृश्य अभी है, ठीक इसी तरह जब मैं प्रदर्शित होने के लिए संकलन विशेष वर्ग से पहले जानने के बिना आवेदन के प्रमुख घटकों को लोड करने की कोशिश कर रहा हूं। हालांकि इस विशेष मामले को गतिशील घटक निर्माण की आवश्यकता नहीं है।
zii

1

यह सर्वर से उत्पन्न डायनेमिक फॉर्म कंट्रोल का उदाहरण है।

https://stackblitz.com/edit/angular-t3mmg6

यह उदाहरण डायनेमिक है फॉर्म नियंत्रण ऐड घटक में है (यह वह जगह है जहां आप सर्वर से फॉर्मकंट्रोल प्राप्त कर सकते हैं)। यदि आप addcomponent विधि देखते हैं तो आप प्रपत्र नियंत्रण देख सकते हैं। इस उदाहरण में मैं कोणीय सामग्री का उपयोग नहीं कर रहा हूं, लेकिन यह काम करता है (मैं @ काम का उपयोग कर रहा हूं)। यह कोणीय 6 पर लक्षित है, लेकिन सभी पिछले संस्करण में काम करता है।

AngularVersion 5 और इसके बाद के संस्करण के लिए JITComplierFactory जोड़ने की आवश्यकता है।

धन्यवाद

विजय


0

इस विशेष मामले के लिए ऐसा लगता है कि घटक को गतिशील रूप से बनाने के लिए एक निर्देश का उपयोग करना बेहतर विकल्प होगा। उदाहरण:

HTML में जहां आप कंपोनेंट बनाना चाहते हैं

<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>

मैं निम्नलिखित तरीके से निर्देशन को देखूंगा और डिजाइन करूंगा।

const components: {[type: string]: Type<YourConfig>} = {
    text : TextEditorComponent,
    numeric: NumericComponent,
    string: StringEditorComponent,
    date: DateComponent,
    ........
    .........
};

@Directive({
    selector: '[dynamicComponentDirective]'
})
export class DynamicComponentDirective implements YourConfig, OnChanges, OnInit {
    @Input() yourConfig: Define your config here //;
    component: ComponentRef<YourConfig>;

    constructor(
        private resolver: ComponentFactoryResolver,
        private container: ViewContainerRef
    ) {}

    ngOnChanges() {
        if (this.component) {
            this.component.instance.config = this.config;
            // config is your config, what evermeta data you want to pass to the component created.
        }
    }

    ngOnInit() {
        if (!components[this.config.type]) {
            const supportedTypes = Object.keys(components).join(', ');
            console.error(`Trying to use an unsupported type ${this.config.type} Supported types: ${supportedTypes}`);
        }

        const component = this.resolver.resolveComponentFactory<yourConfig>(components[this.config.type]);
        this.component = this.container.createComponent(component);
        this.component.instance.config = this.config;
    }
}

इसलिए आपके कंपोनेंट्स में टेक्स्ट, स्ट्रिंग, डेट, जो कुछ भी - जो भी ng-containerएलिमेंट आप HTML में पास कर रहे हैं वह एलिमेंट में उपलब्ध होगा।

विन्यास, yourConfigसमान हो सकता है और आपकी मेटाडेटा को परिभाषित कर सकता है।

आपके कॉन्फ़िगरेशन या इनपुट प्रकार के आधार पर निर्देश के अनुसार कार्य करना चाहिए और समर्थित प्रकारों से, यह उपयुक्त घटक को प्रस्तुत करेगा। यदि नहीं, तो यह एक त्रुटि लॉग करेगा।


-1

ओफिर स्टर्न के जवाब के शीर्ष पर बिल्डिंग, यहां एक वैरिएंट है जो एंगुलर 4 में एओटी के साथ काम करता है। मेरे पास एकमात्र मुद्दा यह है कि मैं किसी भी सेवाओं को डायनामिककंपोनेंट में इंजेक्ट नहीं कर सकता, लेकिन मैं इसके साथ रह सकता हूं।

नोट: मैंने कोणीय 5 के साथ परीक्षण नहीं किया है।

import { Component, OnInit, Input, NgModule, NgModuleFactory, Compiler, EventEmitter, Output } from '@angular/core';
import { JitCompilerFactory } from '@angular/compiler';

export function createJitCompiler() {
  return new JitCompilerFactory([{
    useDebug: false,
    useJit: true
  }]).createCompiler();
}

type Bindings = {
  [key: string]: any;
};

@Component({
  selector: 'app-compile',
  template: `
    <div *ngIf="dynamicComponent && dynamicModule">
      <ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;">
      </ng-container>
    </div>
  `,
  styleUrls: ['./compile.component.scss'],
  providers: [{provide: Compiler, useFactory: createJitCompiler}]
})
export class CompileComponent implements OnInit {

  public dynamicComponent: any;
  public dynamicModule: NgModuleFactory<any>;

  @Input()
  public bindings: Bindings = {};
  @Input()
  public template: string = '';

  constructor(private compiler: Compiler) { }

  public ngOnInit() {

    try {
      this.loadDynamicContent();
    } catch (err) {
      console.log('Error during template parsing: ', err);
    }

  }

  private loadDynamicContent(): void {

    this.dynamicComponent = this.createNewComponent(this.template, this.bindings);
    this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));

  }

  private createComponentModule(componentType: any): any {

    const runtimeComponentModule = NgModule({
      imports: [],
      declarations: [
        componentType
      ],
      entryComponents: [componentType]
    })(class RuntimeComponentModule { });

    return runtimeComponentModule;

  }

  private createNewComponent(template: string, bindings: Bindings): any {

    const dynamicComponent = Component({
      selector: 'app-dynamic-component',
      template: template
    })(class DynamicComponent implements OnInit {

      public bindings: Bindings;

      constructor() { }

      public ngOnInit() {
        this.bindings = bindings;
      }

    });

    return dynamicComponent;

  }

}

उम्मीद है की यह मदद करेगा।

चीयर्स!

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.