Wie kann in Angular 2 überprüft werden, ob <ng-content> leer ist?

88

Angenommen, ich habe eine Komponente:

@Component({
    selector: 'MyContainer',
    template: `
    <div class="container">
        <!-- some html skipped -->
        <ng-content></ng-content>
        <span *ngIf="????">Display this if ng-content is empty!</span>
        <!-- some html skipped -->
    </div>`
})
export class MyContainer {
}

Jetzt möchte ich einige Standardinhalte anzeigen, wenn <ng-content>diese Komponente leer ist. Gibt es eine einfache Möglichkeit, dies zu tun, ohne direkt auf das DOM zuzugreifen?

Slawomir Dadas
quelle
1
Mögliches Duplikat von Wie man prüft, ob ng-Inhalt existiert
titusfx
Zu Ihrer Information, ich weiß, dass die akzeptierte Antwort funktioniert, aber ich denke, es ist besser, einen Eingabeparameter vom Typ "useDefault" an Komponenten zu übergeben, der standardmäßig auf false gesetzt ist.
Bryan60

Antworten:

94

Schließen Sie ng-contentein HTML-Element wie ein ein div, um einen lokalen Verweis darauf zu erhalten, und binden Sie den ngIfAusdruck dann an ref.children.length == 0:

template: `<div #ref><ng-content></ng-content></div> 
           <span *ngIf="ref.nativeElement.childNodes.length == 0">
              Display this if ng-content is empty!
           </span>`
Pixelbits
quelle
20
Gibt es keinen alternativen Weg dazu? Weil dies hässlich ist, verglichen mit Aurelia Fallback Slots
Astronaut
15
Es ist sicherer zu bedienen ref.children.length. childNodes enthält textKnoten, wenn Sie Ihren HTML- Code mit Leerzeichen oder neuen Zeilen formatieren, untergeordnete Knoten jedoch weiterhin leer sind.
Parlament
5
Es gibt eine Funktionsanforderung für eine bessere Methode für den Angular Issue Tracker: github.com/angular/angular/issues/12530 (möglicherweise lohnt es sich, dort eine +1 hinzuzufügen).
Eppsilon
5
Ich wollte gerade posten, dass dies nicht zu funktionieren schien, bis mir klar wurde, dass ich das Beispiel von ref.childNodes.length == 0anstelle von verwendet habe ref.children.length == 0. Es würde einem Haufen helfen, wenn Sie die Antwort so bearbeiten könnten, dass sie konsistent ist. Einfacher Fehler, dich nicht zu verprügeln. :)
Dustin Cleveland
2
Es ist gerade ref.nativeElement.childNodes.length. Könnte diese Antwort bitte bearbeitet werden?
Romain Bruckert
33

In @pixelbits fehlen einige Antworten. Wir müssen nicht nur die childrenEigenschaft überprüfen , da Zeilenumbrüche oder Leerzeichen in der übergeordneten Vorlage ein childrenElement mit leerem Text \ Zeilenumbruch verursachen. Besser zu überprüfen .innerHTMLund .trim()es.

Arbeitsbeispiel:

<span #ref><ng-content></ng-content></span>
<span *ngIf="!ref.innerHTML.trim()">
    Content if empty
</span>
lfoma
quelle
1
beste Antwort für mich :)
Kamil Kiełczewski
Können wir mit dieser Methode prüfen, ob das untergeordnete Element leer ist, und das übergeordnete Element ausblenden, wenn dies der Fall ist? Ich habe versucht, kein Glück
Kavinda Jayakody
@KavindaJayakody Ja, dies überprüft das untergeordnete Element auf leer. Zeigen Sie Ihren Code.
lfoma
* ngIf = "! ref.innerHTML.trim ()" Dieser Teil verursacht Leistungsprobleme. Trimmen ist O (n).
RandomCode
26

EDIT 17.03.2020

Reines CSS (2 Lösungen)

Stellt Standardinhalte bereit, wenn nichts in ng-content projiziert wird.

Mögliche Selektoren:

  1. :only-childWähler. Siehe diesen Beitrag hier :: only-child Selector

    Dieser erfordert weniger Code / Markup. Unterstützung seit IE 9: Kann ich verwenden: nur Kind

  2. :emptyWähler. Lesen Sie einfach weiter.

    Unterstützung von IE 9 und teilweise seit IE 7/8: https://caniuse.com/#feat=css-sel3

HTML

<div class="wrapper">
    <ng-content select="my-component"></ng-content>
</div>
<div class="default">
    This shows something default.
</div>

CSS

.wrapper:not(:empty) + .default {
    display: none;
}

Falls es nicht funktioniert

Beachten Sie, dass mindestens ein Leerzeichen nicht leer ist. Angular entfernt Leerzeichen, aber nur für den Fall, dass dies nicht der Fall ist:

<div class="wrapper"><!--
    --><ng-content select="my-component"></ng-content><!--
--></div>

oder

<div class="wrapper"><ng-content select="my-component"></ng-content</div>
Stefan Rein
quelle
1
Dies war bei weitem die beste Lösung für meinen Anwendungsfall. Ich erinnerte mich nicht daran, dass wir eine emptyCSS-Pseudoklasse hatten
Julianobrasil
@Stefan Der Standardinhalt wird sowieso gerendert ... er wird nur ausgeblendet. Für komplexe Standardinhalte ist dies also nicht die beste Lösung. Ist das richtig?
BossOz
1
@BossOz Du hast recht. Es wird gerendert (Konstruktor usw. wird aufgerufen, wenn Sie eine Winkelkomponente haben). Diese Lösung funktioniert nur für dumme Ansichten. Wenn Sie eine komplexe Logik haben, die das Laden oder ähnliches beinhaltet, ist es meiner Meinung nach am besten, eine strukturelle Direktive zu schreiben, die eine Vorlage / Klasse erhalten kann (wie auch immer Sie sie implementieren möchten) und dann abhängig von der Logik die zu rendern gewünschte Komponente oder die Standardkomponente. Der Kommentarbereich ist für ein Beispiel viel zu klein. Ich kommentiere noch einmal ein anderes Beispiel, das wir in einer unserer Apps haben.
Stefan Rein
Eine Möglichkeit wäre, dass eine Komponente die ContentChildren über eine Direktive auswählt (Abfrage mit DirectiveClass, aber Verwendung als strukturelle Direktive, sodass kein anfängliches Bootstrapping nur mit dem Markup verbunden ist): <loader [data]="allDataWeNeed"> <desired-component *loadingRender><desired-component> <other-default-component *renderWhenEmptyResult></other-default-component> </loader>In der Ladekomponente können Sie währenddessen die wahrgenommene Leistungsbelastung anzeigen Die Daten werden geladen. Sie können es auf verschiedene Arten schreiben / lösen. Dies ist eine Demonstration, wie Sie es lösen können.
Stefan Rein
20

Wenn Sie den Inhalt einfügen, fügen Sie eine Referenzvariable hinzu:

<div #content>Some Content</div>

und in Ihrer Komponentenklasse erhalten Sie mit @ContentChild () einen Verweis auf den injizierten Inhalt

@ContentChild('content') content: ElementRef;

In Ihrer Komponentenvorlage können Sie also überprüfen, ob die Inhaltsvariable einen Wert hat

<div>
  <ng-content></ng-content>
  <span *ngIf="!content">
    Display this if ng-content is empty!
  </span>    
</div> 
Lerner
quelle
Dies ist die sauberste und viel bessere Antwort. Es unterstützt die ContentChild-API ab Werk. Ich kann nicht verstehen, warum die Top-Antwort so viele Stimmen bekommen hat.
CarbonDry
9
Das OP fragte, ob ngContent leer sei oder nicht - das heißt <MyContainer></MyContainer>. Ihre Lösung erwartet, dass Benutzer unter MyContainer ein Unterelement erstellen : <MyContainer><div #content></div></MyContainer>. Obwohl dies eine Möglichkeit ist, würde ich nicht sagen, dass dies überlegen ist.
Pixelbits
6

Injizieren elementRef: ElementRefund prüfen, ob elementRef.nativeElementKinder vorhanden sind. Dies funktioniert möglicherweise nur mit encapsulation: ViewEncapsulation.Native.

Wickeln Sie das <ng-content>Tag ein und prüfen Sie, ob es Kinder hat. Das funktioniert nicht mit encapsulation: ViewEncapsulation.Native.

<div #contentWrapper>
  <ng-content></ng-content>
</div>

und überprüfen Sie, ob es Kinder hat

@ViewChild('contentWrapper') contentWrapper;

ngAfterViewInit() {
  contentWrapper.nativeElement.childNodes...
}

(nicht getestet)

Günter Zöchbauer
quelle
3
Ich habe dies wegen der Verwendung von abgelehnt @ViewChild. Während "Legal", sollte ViewChild nicht für den Zugriff auf untergeordnete Knoten innerhalb einer Vorlage verwendet werden. Dies ist ein Antimuster, da Eltern zwar etwas über Kinder wissen , sie sich jedoch nicht an sie koppeln sollten , da dies normalerweise zu einer pathologischen Kopplung führt . Eine geeignetere Form des Datentransports oder der Bearbeitung von Anforderungen ist die Verwendung ereignisgesteuerter Methoden .
Cody
4
@Cody, danke, dass du einen Kommentar zu deiner Ablehnung abgegeben hast. Eigentlich folge ich Ihrer Argumentation nicht. Die Vorlage und die Komponentenklasse sind eine Einheit - eine Komponente. Ich verstehe nicht, warum der Zugriff auf die Vorlage über den Code ein Antimuster sein sollte. Angular (2/4) hat fast nichts mit AngularJS gemeinsam, außer dass beide Web-Frameworks und der Name sind.
Günter Zöchbauer
2
Gunter, du machst zwei wirklich gute Punkte. Angular sollte nicht unbedingt mit einem Stigma aus den Wegen von AngularJS schlüpfen. Aber ich würde markieren, dass der erste Punkt (und die, die ich oben gemacht habe) in die philosophische Gosse der Technik fällt. Trotzdem bin ich so etwas wie ein Experte für Software-Kopplung geworden und würde von einer [zumindest starken] Verwendung abraten @ViewChild. Vielen Dank, dass Sie meinen Kommentaren etwas Ausgewogenheit verliehen haben. Ich finde unsere beiden Kommentare genauso erwägenswert wie die anderen.
Cody
2
@Cody vielleicht kommt deine starke Meinung über @ViewChild()vom Namen. "Kinder" sind nicht unbedingt Kinder, sie sind nur der deklarative Teil der Komponente (wie ich es sehe). Wenn es sich bei den für dieses Markup erstellten Komponenteninstanzen um Zugriffe handelt, ist dies natürlich eine andere Geschichte, da hierfür zumindest Kenntnisse über die öffentliche Schnittstelle erforderlich wären und dies tatsächlich zu einer engen Kopplung führen würde. Dies ist eine sehr interessante Diskussion. Es macht mir Sorgen, dass ich nicht genug Zeit habe, um über solche Dinge nachzudenken, weil bei solchen Frameworks zu oft Zeit verbrannt wird, um überhaupt einen Weg zu finden.
Günter Zöchbauer
3
Dies in der API nicht verfügbar zu machen, ist das eigentliche Anti-Pattern :-(
Simon_Weaver
3

Wenn Sie einen Standardinhalt anzeigen möchten, verwenden Sie nicht einfach den Selektor "Nur Kind" von CSS.

https://www.w3schools.com/cssref/sel_only-child.asp

für zB: HTML

<div>
  <ng-content></ng-content>
  <div class="default-content">I am deafult</div>
</div>

CSS

.default-content {
   display: none;
}

.default-content:only-child {
   display: block;
}
Ökosystem31
quelle
1

In meinem Fall muss ich Eltern von leeren ng-Inhalten verstecken:

<span class="ml-1 wrapper">
  <ng-content>
  </ng-content>
</span>

Einfache CSS funktioniert:

.wrapper {
  display: inline-block;

  &:empty {
    display: none;
  }
}
smg
quelle
1

Ich habe eine Lösung mit @ContentChildren Decorator implementiert , die der Antwort von @ Lerner irgendwie ähnlich ist.

Laut Docs hat dieser Dekorateur:

Rufen Sie die QueryList der Elemente oder Anweisungen aus dem Inhalts-DOM ab. Jedes Mal, wenn ein untergeordnetes Element hinzugefügt, entfernt oder verschoben wird, wird die Abfrageliste aktualisiert, und die in der Abfrageliste beobachtbaren Änderungen geben einen neuen Wert aus.

Der erforderliche Code in der übergeordneten Komponente lautet also:

<app-my-component>
  <div #myComponentContent>
    This is my component content
  </div>
</app-my-component>

In der Komponentenklasse:

@ContentChildren('myComponentContent') content: QueryList<ElementRef>;

Dann in der Komponentenvorlage:

<div class="container">
  <ng-content></ng-content>
  <span *ngIf="*ngIf="!content.length""><em>Display this if ng-content is empty!</em></span>
</div>

Vollständiges Beispiel : https://stackblitz.com/edit/angular-jjjdqb

Ich habe diese Lösung in Winkelkomponenten, implementiert gefunden matSuffix, in der Form-Feld - Komponente.

In der Situation, in der der Inhalt der Komponente später nach der Initialisierung der App eingefügt wird, können wir auch eine reaktive Implementierung verwenden, indem wir das changesEreignis abonnieren QueryList:

export class MyComponentComponent implements AfterContentInit, OnDestroy {
  private _subscription: Subscription;
  public hasContent: boolean;

  @ContentChildren('myComponentContent') content: QueryList<ElementRef>;

  constructor() {}

  ngAfterContentInit(): void {
    this.hasContent = (this.content.length > 0);
    this._subscription = this.content.changes.subscribe(() => {
      // do something when content updates
      //
      this.hasContent = (this.content.length > 0);
    });
  }

  ngOnDestroy() {
    this._subscription.unsubscribe();
  }

}

Vollständiges Beispiel : https://stackblitz.com/edit/angular-essvnq

andreivictor
quelle
0

Mit Angular 10 hat es sich leicht geändert. Sie würden verwenden:

<div #ref><ng-content></ng-content></div> 
<span *ngIf="ref.children.length == 0">
  Display this if ng-content is empty!
</span>
John Hamm
quelle