Selección dinámica de componentes en Angular2




sintaxis angular (3)

Esta pregunta ya tiene una respuesta aquí:

Dado un modelo para una sección de página que contiene múltiples campos y se completará con datos como este:

{
    "fields": [
        {
            "id": 1,
            "type": "text",
            "caption": "Name",
            "value": "Bob"
        },
        {
            "id": 2,
            "type": "bool",
            "caption": "Over 24?",
            "value": 0
        },
        {
            "id": 3,
            "type": "options",
            "options" : [ "M", "F"],
            "caption": "Gender",
            "value": "M"
        }
    ]
}

Me gustaría tener un Componente de sección genérico que no conozca los diferentes tipos de campos que podría abarcar, para evitar una gran cantidad de lógica condicional en la plantilla de sección, y para agregar nuevas vistas / componentes de tipo de campo añadidos al colocar un auto contenida en lugar de tener que modificar un componente por separado.

Mi ideal sería que el selector de un Componente fuera lo suficientemente específico como para lograrlo seleccionando un elemento en la plantilla del Componente principal en función de los valores de los atributos vinculados al modelo. Por ejemplo: (disculpe cualquier problema de sintaxis ya que lo codifiqué en la ventana SO, la parte principal a la que debe prestar atención es el selector en BooleanComponent.ts

SectionComponent.ts

@Component({
    selector: 'my-app'
})
@View({
    template: `
        <section>
            <div *ng-for="#field of fields">
                <field type="{{field.type}}"></field>
            </div>  
        </section>
    `,
    directives: [NgFor]
})
class SectionComponent {
    fields: Array<field>;
    constructor() {
        this.fields = // retrieve section fields via service
    }
}

FieldComponent.ts:

// Generic field component used when more specific ones don't match
@Component({
    selector: 'field'
})
@View({
    template: `<div>{{caption}}: {{value}}</div>`
})
class FieldComponent {
    constructor() {}
}

BooleanComponent.ts:

// specific field component to use for boolean fields
@Component({
    selector: 'field[type=bool]'
})
@View({
    template: `<input type="checkbox" [id]="id" [checked]="value==1"></input>`
})
class BooleanComponent {
    constructor() {}
}

... y con el tiempo agregaría nuevos componentes para proporcionar plantillas y comportamientos especiales para otros tipos de campos específicos, o incluso campos con ciertos subtítulos, etc.

Esto no funciona porque los selectores de componentes deben ser un nombre de elemento simple (al menos en alpha.26 y alpha.27). Mi investigación de las conversaciones de Github me llevó a creer que esta restricción se estaba relajando, pero no puedo determinar si lo que quiero hacer realmente será compatible.

Alternativamente, he visto un DynamicComponentLoader mencionado, aunque ahora no puedo encontrar el ejemplo que pensé que estaba en la guía angular.io. Aún así, no sé cómo se podría usar para cargar dinámicamente un componente para el que no conoce el nombre ni los criterios de coincidencia.

¿Hay alguna manera de lograr mi objetivo de desacoplar componentes especializados de sus padres usando una técnica similar a la que probé, o alguna otra técnica que desconozco en Angular 2?

ACTUALIZAR 2015-07-06

http://plnkr.co/edit/fal9OA7ghQS1sRESutGd?p=preview

Pensé que era mejor mostrar los errores que encuentro más explícitamente. He incluido un plunk con algún código de muestra, aunque solo el primero de estos 3 errores será visible, ya que cada uno bloquea al otro para que solo pueda presumir uno a la vez. He codificado duro para obtener # 2 y # 3 por el momento.

  1. Si mi selector en BooleanComponent es selector: 'field[type=bool]' o selector: '[type=bool]' , obtengo una pila de error de Angular

    El componente 'BooleanComponent' solo puede tener un selector de elemento, pero tenía '[type = bool]'

  2. <field [type]="field.type"></field> no vincula mi valor field.type con el atributo type, pero me da este error (que afortunadamente aparece ahora en alpha 28. En alpha 26 estaba anteriormente encendido, falló silenciosamente). Puedo deshacerme de este error al agregar una propiedad de tipo a mi FieldComponent y BooleanComponent derivado y conectarlo con la colección de propiedades de @Component, pero no lo necesito para nada en los componentes.

    No se puede vincular a 'tipo' ya que no es una propiedad conocida del elemento 'campo' y no hay directivas coincidentes con la propiedad correspondiente

  3. Me veo obligado a enumerar FieldComponent y BooleanComponent en la lista de directivas de mi anotación de SectionComponent View, o no se encontrarán ni se aplicarán. Leí las discusiones de diseño del equipo de Angular donde tomaron esta decisión consciente a favor de la explícita para reducir la ocurrencia de colisiones con las directivas en bibliotecas externas, pero rompe la idea de los componentes integrales que estoy tratando de lograr.

En este punto, estoy luchando para ver por qué Angular2 incluso se molesta en tener selectores. El componente padre ya tiene que saber qué componentes secundarios tendrá, adónde irán y qué datos necesitan. Los selectores son totalmente superfluos en este momento, podría ser una convención de emparejamiento de nombres de clase. No veo la abstracción entre los componentes que necesitaría para desacoplarlos.

Debido a estas limitaciones en la capacidad del framework Angular2, estoy haciendo mi propio esquema de registro de componentes y ubicándolos a través de DynamicComponentLoader, pero todavía estaría muy curioso de ver respuestas para las personas que han encontrado una mejor manera de lograr esto. .


Me tomó un tiempo, pero veo lo que estás tratando de hacer. Esencialmente crea una biblioteca de formas imperativas para ng2, como angulosas. Lo que intenta hacer no está cubierto en los documentos, pero podría solicitarse. Las opciones actuales se pueden encontrar debajo de las anotaciones de las líneas 419 a 426 .

Esta solución se seleccionará en exceso y dará lugar a algunos falsos positivos, pero pruébelo.

Cambie <field type="{{field.type}}"></field> a <field [type]="field.type"></field>

Luego seleccione por atributo:

@Component({
  selector: '[type=bool]'
})

Supongo que podría usar Query / ViewQuery y QueryList para buscar todos los elementos, suscribirse a los cambios de QueryList y filtrar elementos por cualquier atributo, smth. Me gusta esto:

constructor(@Query(...) list:QueryList<...>) {
   list.changes.subscribe(.. filter etc. ..);
}

Y si desea vincular al tipo, debe hacerlo así:

  <input [attr.type]="type"/>

  1. Como dice el error, un componente debe tener un selector único. Si desea vincular el comportamiento del componente al selector de atributos como con [type='bool'] , tendría que usar directivas. Use selector='bool-field' para su BoolComponent .

  2. Como dice el error, su componente <field> genérico no tiene un atributo de type al que vincularse. Puede solucionarlo agregando una variable de miembro de entrada: @Input() type: string;

  3. Desea delegar la plantilla del componente en un único componente que reciba un atributo de type . Simplemente cree ese componente genérico y otros componentes que lo usen solo deberán proporcionarlo, no sus elementos secundarios.

Ejemplo: http://plnkr.co/edit/HUH8fm3VmscsK3KEWjf6?p=preview

@Component({
  selector: 'generic-field',
  templateUrl: 'app/generic.template.html'
})
export class GenericFieldComponent {
  @Input() 
  fieldInfo: FieldInfo;
}

usando la plantilla:

<div>
  <span>{{fieldInfo.caption}}</span>

  <span [ngSwitch]="fieldInfo.type">
    <input  *ngSwitchWhen="'bool'" type="checkbox" [value]="fieldInfo.value === 1">  
    <input  *ngSwitchWhen="'text'" type="text" [value]="fieldInfo.value">  
    <select  *ngSwitchWhen="'options'" type="text" [value]="fieldInfo.value">
      <option *ngFor="let option of fieldInfo.options" >
        {{ option }}
      </option>
    </select>  
  </span>
</div>




angular