1
votes

Comment appeler ng-template par variable?

J'ai vérifié: ViewChildren pour ng-template et Accéder à plusieurs viewchildren en utilisant @viewchild

Mais je ne peux pas pour appeler mon template via la valeur d'une variable ...

Donc mon template est comme ceci:

export class SomeClass {
   @ViewChildren(TemplateRef) templates: QueryList<TemplateRef<any>>;
}

features est une énumération avec des valeurs correspondantes aux noms de mes modèles ... puis dans ma classe, j'ai essayé de saisir tous les ViewChildren comme ceci:

<ng-container *ngFor="let feature of Object.values(features)">
  <ng-container *ngTemplateOutlet="templates[feature]"></ng-container>
</ng-container>

<ng-template #myFeature>
  Nothing to see here
</ng-template>

<ng-template #myOtherFeature>
  Nothing to see here
</ng-template>

Donc l'idée est que, j'ai pensé que je devrais être capable de référencer le bon modèle en faisant des templates [feature] qui devraient donner quelque chose comme templates ['myFeature'] et donner moi le bon modèle ... mais ne l'est pas.

Comment puis-je archiver cela?


3 commentaires

[ngTemplateOutlet] = "modèles ['myFeature']"?


Si tout ce dont vous avez besoin est [ngTemplateOutlet] = "templates ['myFeature']" alors ne pouvez-vous pas simplement avoir tous les noms de template dans une chaîne [] ie public templates: string [] = ['myFeature', ' myOtherFeature ']


Existe-t-il un moyen d'éviter de définir manuellement les modèles disponibles? C'est ce que j'ai essayé d'éviter avec la variable templates .


3 Réponses :


1
votes

Puisque vous avez créé différents modèles (différentes variables de modèle), vous devez créer un enfant de vue différent pour chacun d'eux. ViewChildren ne fonctionnera que s'ils sont de la même variable de référence de modèle. Et l'utilisation dans votre code, il récupérera chaque instance de modèle parce que vous passez TemplateRef , il récupérera chaque instance de ce type.

J'ai créé un stackblitz, qui illustre cela .

Notez également que votre instance de modèle ne sera disponible que sur ngAfterViewInit () , jusque-là elle ne serait pas définie.


3 commentaires

Existe-t-il un moyen d'éviter de définir un ViewChild pour chaque modèle que j'ai? Je trouve ça assez stupide? Je veux dire que je peux aussi utiliser un commutateur et appeler le modèle moi-même manuellement?


Non je ne pense pas. Peut-être que quelqu'un d'autre pourrait avoir une approche différente.


@codenamezero Voir ma réponse ci-dessous pour obtenir ce que vous recherchez avec une seule variable



0
votes

Après quelques bricolages dans le ngAfterViewInit , je l'ai fait fonctionner comme je le souhaite. C'est un peu moche car j'ai besoin d'utiliser un setTimeout et j'ai besoin de jouer avec les variables internes (je ne sais pas si c'est une bonne idée) ...

Voici un stackblitz la sélection de modèles dynamiques de vitrine et le rendu par valeur de variable.

En un mot, voici comment je l'ai fait, vous avez besoin de 3 choses:

ngAfterViewInit(): void {
  // setTimeout to bypass the ExpressionChangedAfterItHasBeenCheckedError
  setTimeout(() => {
    // loop through the fetched template
    this.templates.toArray().forEach(t => {
      // for those ng-template that has a #name, they will have references
      const keys = Object.keys((t as any)._def.references);
      if (keys.length === 1) {
        // so we put these in the templateMap
        this.templateMap[keys[0]] = t;
      }
    });
    // now we change it to ready, so it would render it
    this.templateMapReady = true;
  });
}

Ensuite, dans le ngAfterViewInit , vous faites ce qui suit pour construire le templateMap :

// to grab all the templates
@ViewChildren(TemplateRef) templates: QueryList<TemplateRef<any>>;
// to be use for template selection
templateMap: { [key: string]: TemplateRef<any> } = {};
// to avoid rendering during the first run when templateMap is not yet ready
templateMapReady = false;


4 commentaires

Utiliser un délai d'expiration pour contourner la détection de changement semble un peu faux. Utilisez simplement un crochet de cycle de vie différent


De plus, l'utilisation de détails d'implémentation privée est mauvaise.


J'ai depuis ajouté une meilleure solution de contournement pour utiliser le rappel onStable de NgZone.


Salut, je ne vois pas le nouveau changement ni dans votre message ni dans votre exemple StackBlitz. Néanmoins, vérifiez ma réponse modifiée ci-dessus pour une solution plus concise et cohérente



0
votes

EXPLICATION

La directive ViewChildren verra sa valeur stockée juste avant ngAfterViewInit () ( voir ceci ).

Angular vérifie d'abord votre modèle et trouve que modèles est undefined .

Il commence alors à rendre la vue. Dans le processus, il résout les directives de modèle comme ViewChildren () et appelle ngAfterViewInit().

Dans le processus, modèles code > est défini, ce qui signifie que la vue est maintenant dans un état incohérent .

Le rendu initial de la page entraîne une modification de la page elle-même strong> .

C'est là que vous obtenez la tristement célèbre erreur "L'expression a changé ..." .

SOLUTION

Vous ne pouvez pas changer le moment où templates est défini, car il est orchestré par Angular.
Ce que vous pouvez faire cependant, c'est utiliser à la place une autre variable pour la liaison, et la définir sur templates une fois le rendu de vue initial terminé.

Essayer de définir notre nouvelle variable dans ngAfterViewInit () déclenchera à nouveau l'erreur "Expression has changed", puisque ce hook de cycle de vie fait lui-même partie du rendu initial. p >

La solution est de reporter le paramétrage de la nouvelle variable dans ngAfterViewInit () au prochain tour de VM.

Pour ce faire, nous pouvons simplement utiliser setTimeout () sans deuxième argument:

export class AppComponent implements AfterViewInit {
  @ViewChildren(TemplateRef) templates!: QueryList<TemplateRef<any>>;
  features: TemplateRef<any>[] = [];
  name = "Angular";

  ngAfterViewInit() {
    setTimeout(() => this.features = this.templates.toArray());
  }
}

Voir ceci exemple de stackblitz


0 commentaires