web-dev-qa-db-de.com

Angular2: Vorlage einer Komponente rendern/neu laden

Im Idealfall müsste ich die Vorlage meiner Komponente neu laden/rerender. Wenn es jedoch einen besseren Weg gibt, werde ich sie gerne implementieren.

Gewünschtes Verhalten:

Ich habe also eine Komponente für ein Menüelement. Wenn (in einer anderen Komponente) auf eine IBO geklickt wird (irgendeine Art von 'Client') geklickt wird, muss hinzufügen (ich verwende *ngIf) eine neue Option im Menü, die IBO Details und eine untergeordnete Liste wäre.

IBOsNavigationElement Komponente (Menükomponente):

@Component({
    selector: '[ibos-navigation-element]',
    template: `
        <a href="#"><i class="fa fa-lg fa-fw fa-group"></i> <span
                class="menu-item-parent">{{'IBOs' | i18n}}</span>
        </a>
        <ul>
            <li routerLinkActive="active">
                <a routerLink="/ibos/IBOsMain">{{'IBOs Main' | i18n}} {{id}}</a>
            </li>
            <li *ngIf="navigationList?.length > 0">
                <a href="#">{{'IBO Details' | i18n}}</a>
                <ul>
                    <li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
                        <a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
                    </li>
                </ul>
            </li>
        </ul>
    `
})
export class IBOsNavigationElement implements OnInit {
    private navigationList = <any>[];


    constructor(private navigationService: NavigationElementsService) {
        this.navigationService.updateIBOsNavigation$.subscribe((navigationData) => {
                this.navigationList.Push(navigationData);
                log.d('I received this Navigation Data:', JSON.stringify(this.navigationList));
            }
        );
    }

    ngOnInit() {
    }
}

Anfangs ist navigationList leer [], da der Benutzer auf keine IBO (client) geklickt hat. Sobald jedoch eine IBO angeklickt wird, wird die Liste ausgefüllt. Daher brauche ich die neue Option im Menü erscheinen.

Wenn ich mit diesem Code auf eine IBO klicke, werden das <li>-Element und die untergeordneten Elemente erstellt, aber: Ich sehe nur das <li>-Element.

Problem:

Die Menüoption wird generiert, aber nicht von den Layoutstilen verarbeitet. Es muss mit allen Elementen initialisiert werden, um zu wissen, wie die Menüoptionen angezeigt werden.

Ich muss die Vorlage dieser Komponente neu laden, um das Menü korrekt anzuzeigen.

HINWEIS:

Wenn ich die Vorlage ohne den *ngIf verwende, funktioniert das gut, aber ich hätte von Anfang an eine IBO Details -Option, die keinen Sinn hat, da bei der Initialisierung keine IBO angeklickt wurde.

template: `
        <a href="#"><i class="fa fa-lg fa-fw fa-group"></i> <span
                class="menu-item-parent">{{'IBOs' | i18n}}</span>
        </a>
        <ul>
            <li routerLinkActive="active">
                <a routerLink="/ibos/IBOsMain">{{'IBOs Main' | i18n}} {{id}}</a>
            </li>
            <li>
                <a href="#">{{'IBO Details' | i18n}}</a>
                <ul>
                    <li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
                        <a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
                    </li>
                </ul>
            </li>
        </ul>
    `

Gewünschte Ausgabe:

Bevor Sie auf etwas klicken (in init):

 enter image description here

Nachdem ich auf eine IBO geklickt habe (Client):

 enter image description here

Update 1:

Um zu klären, was ich meinte mit: 

Die Menüoption wird generiert, aber nicht von den Layoutstilen verarbeitet

Wenn, wird meine Menükomponente ohne den *ngIf initialisiert:

<li>
    <a href="#">{{'IBO Details' | i18n}}</a>
        <ul>
            <li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
                <a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
            </li>
        </ul>
</li>

Dann können die Layoutstile die Menüausgabe (siehe Bilder) entsprechend dieser Struktur generieren:

<li>
   <a>
   <ul>
      <li *ngFor>
         <a>
      </li>
   </ul>
</li>

Fügen Sie daher das +-Symbol und das Verhalten des Untermenüs usw. hinzu.

Wenn sie jedoch ohne alle Elemente initialisiert wird (wenn *ngIffalse ist, werden <li> und die untergeordneten Elemente nicht gerendert, sodass das Layout sie nicht berücksichtigt, um das Menü zu zeichnen/zu erstellen) und diese Elemente nach dem Rendering hinzugefügt werden , dann sind sie im Quellcode vorhanden, aber wir können sie nicht im Menü sehen, weil:

  • Kein + erstellt
  • Kein Untermenüverhalten
5
SrAxi

Ich habe es endlich geschafft!

Mein Problem war also:

Wenn die neuen HTML-Elemente generiert werden (mit *ngIf) , werden sie nicht angezeigt, weil sie nicht erhalten ) verarbeitet wie die anderen Menüelemente.

Also habe ich gefragt, wie ich die Vorlage mit allen 'neuen' Elementen neu laden oder rendern soll ... Aber ich habe nicht gefunden, wo ich eine Komponente oder die Vorlage einer Komponente neu laden soll. As stattdessen Ich habe die Logik, die das Menü verarbeitet, auf meine aktualisierte Vorlage angewendet.

(Wenn du die Kurzgeschichtenversion haben willst, gehe nach unten und lies die Zusammenfassung)

Also habe ich mich mit der tiefsten Logik meiner Vorlage befasst und eine Anweisung zum Rendern des Menüs erstellt:

MenuDirective (Direktive)

@Directive({
    selector: '[menuDirective]'
})
export class MenuDirective implements OnInit, AfterContentInit {

  constructor(private menu: ElementRef,
            private router: Router,
            public layoutService: LayoutService) {
    this.$menu = $(this.menu.nativeElement);
  }

  // A lot of boring rendering of layout

  ngAfterContentInit() {
        this.renderSubMenus(this.$menu);
    }

  renderSubMenus(menuElement) {
            menuElement.find('li:has(> ul)').each((i, li) => {
                let $menuItem = $(li);
                let $a = $menuItem.find('>a');
                let sign = $('<b class="collapse-sign"><em class="fa fa-plus-square-o"/></b>');
                $a.on('click', (e) => {
                    this.toggle($menuItem);
                    e.stopPropagation();
                    return false;
                }).append(sign);
            });
    }
}

Hier erstelle ich also die menu-Direktive, die das Layout des Menüs gemäß den vorhandenen HTML-Elementen darstellt. Und, wie Sie sehen können, Ich habe das Verhalten, das die Menüelemente verarbeitet, isoliert ​​Hinzufügen des Symbols +, Erstellen der Untermenüfunktion usw. ...: renderSubMenus() .

Wie verhält sich renderSubMenus():

Es durchläuft die DOM-Elemente des als Parameter übergebenen nativeElement und wendet die Logik an, um das Menü korrekt anzuzeigen.

menu.html

<ul menuDirective>    
    <li ibos-navigation-element></li>
    <li>
        <a href="#"><i class="fa fa-lg fa-fw fa-shopping-cart"></i> <span
                            class="menu-item-parent">{{'Orders' | i18n}}</span></a>
        <ul>
            <li routerLinkActive="active">
                <a routerLink="/orders/ordersMain">{{'Orders' | i18n}}</a>
            </li>
        </ul>
    </li>        
</ul>

Und so würde ich das Menü zusammenstellen.

Sehen wir uns nun die Komponente IBOsNavigationElement an, die im Menü mit dem Attribut [ibos-navigation-element] Enthalten ist.

IBOsNavigationElement (Komponente)

@Component({
    selector: '[ibos-navigation-element]',
    template: `
        <a href="#"><i class="fa fa-lg fa-fw fa-group"></i> <span
                class="menu-item-parent">{{'IBOs' | i18n}}</span>
        </a>
        <ul class="renderMe">
            <li routerLinkActive="active">
                <a routerLink="/ibos/IBOsMain">{{'IBOs Main' | i18n}} {{id}}</a>
            </li>
            <li *ngIf="navigationList?.length > 0">
                <a href="#">{{'IBO Details' | i18n}}</a>
                <ul>
                    <li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
                        <a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
                    </li>
                </ul>
            </li>
        </ul>
    `
})
export class IBOsNavigationElement implements OnInit, DoCheck {
    private $menuElement: any;
    private navigationList = <any>[];
    private menuRendered: boolean = false;

    constructor(private navigationService: NavigationElementsService, private menuDirective: MenuDirective, private menuElement: ElementRef) {
        this.$menuElement = $(this.menuElement.nativeElement);

        this.navigationService.updateIBOsNavigation$.subscribe((navigationData) => {
                this.navigationList.Push(navigationData);
                log.d('I received this Navigation Data:', JSON.stringify(this.navigationList));
            }
        );
    }

    ngOnInit() {
    }

    ngDoCheck() {
        if (this.navigationList.length > 0 && !this.menuRendered) {
            log.er('calling renderSubMenus()');
            this.menuDirective.renderSubMenus(this.$menuElement.find('ul.renderMe'));
            this.menuRendered = true;
        }
    }
}

Ok, was habe ich hier anders gemacht? Verschiedene Dinge...

  1. Ich importiere die Direktive MenuDirective, damit ich ihre Methode renderSubMenus() aufrufen kann.
  2. Ich benutze ElementRef und find(), um den Codeblock auszuwählen, den ich an this.menuDirective.renderSubMenus() senden möchte. Ich finde es durch seine class, siehe: this.$menuElement.find('ul.renderMe').
  3. Ich implementiere Angulars Haken DoCheck , um die gewünschten Änderungen zu erkennen und Logik auf dieses Änderungsereignis anzuwenden. Siehe ngDoCheck() Methode, mit der ich überprüfe, ob die Liste navigationList ausgefüllt ist und ob ich diesen Codeblock bereits gerendert habe (Ich hatte Probleme, weil zu oft gerendert wurde und ich hatte gerne 6 + Tasten: Katastrophe) .

Zusammenfassung:

Um die Vorlage neu zu laden :

  1. Ich habe eine Direktive mit einer Methode erstellt die die Logik anwendet, die normalerweise bei init auftritt.
  2. Ich instanziiere diese Direktive in der Komponente, die ich neu laden möchte.
  3. Mit ElementRef bekomme ich den Teil der Vorlage, den ich 'neu laden' möchte .
  4. Ich wähle, wann ich die 'reload' Methode anwenden möchte, in meinem Fall habe ich es mit ngDoCheck() gemacht. Sie können diese Methode jederzeit aufrufen.
  5. Ich nenne die 'reload ' Methode der Direktive, die als Parameter den Teil des Codes in meiner Vorlage übergibt, den ich neu laden möchte (ich hätte die gesamte Vorlage übergeben können) Wenn ich wollte).
  6. Die Methode wird auf den Teil der Vorlage angewendet, den ich mit derselben Logik gesendet habe, die angewendet worden wäre, wenn ich die Komponente mit den verborgenen Elementen durch *ngIf instanziiert hätte.

Also technisch gesehen Ich habe die Komponente nicht neu geladen. Ich habe auf die Vorlage der Komponente dieselbe Logik angewendet, die angewendet worden wäre, wenn ich sie neu geladen hätte.

2
SrAxi

Angular hat zwei Änderungserkennungsstrategien:

Die OnPush-Strategie kann eine bessere Leistung erzielen, wenn Sie mehrere Elemente auf einer Seite haben oder mehr Kontrolle über den Renderprozess haben möchten.

Um diese Strategie zu verwenden, müssen Sie sie in Ihrer Komponente deklarieren:

@Component({
    selector: '[ibos-navigation-element]',
    template: `...`,
    changeDetection: ChangeDetectionStrategy.OnPush
})

Und spritze in deinen Konstruktor:

constructor(
    private changeDetector: ChangeDetectorRef,
) {}

Wenn Sie die Änderungserkennung auslösen möchten, damit die Komponente erneut gerendert werden kann (in Ihrem Fall direkt nach dem Hinzufügen eines neuen IBO/Clients zum Modell), rufen Sie Folgendes auf:

this.changeDetector.markForCheck();

Überprüfen Sie die Live-Demo aus dem offiziellen Tutorial: http://plnkr.co/edit/GC512b?p=preview

Wenn sich das Problem nicht auf die Änderungserkennung bezieht, sondern mit dem CSS/SCSS-Stil zusammenhängt, beachten Sie, dass in Angular 2 jede Komponente ihre eigenen CSS-Klassen hat und nicht "geerbt" wird. Durch die "Kinder" -Elemente sind sie vollständig voneinander isoliert. Eine Lösung könnte die Schaffung globaler CSS/SCSS-Stile sein.

4
Pedro Penna

Versuchen Sie es mit ChangeDetectorRef.detectChanges () - es funktioniert ähnlich wie $ scope. $ Digest () from Angular 1.

Hinweis: ChangeDetectorRef muss in die Komponente eingefügt werden.

0
n0k