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 Réponses :
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.
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
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;
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
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é ..." .
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
[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
.