W Angular 2 jak sprawdzić, czy <ng-content> jest pusty?

92

Załóżmy, że mam komponent:

@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 {
}

Teraz chciałbym wyświetlić domyślną zawartość, jeśli <ng-content>ten komponent jest pusty. Czy istnieje łatwy sposób na zrobienie tego bez bezpośredniego dostępu do DOM?

Sławomir Dadas
źródło
1
Możliwy duplikat Jak sprawdzić, czy ng-content istnieje
titusfx
FYI, wiem, że zaakceptowana odpowiedź działa, ale myślę, że lepiej jest przekazać parametr wejściowy typu „useDefault” do komponentów, domyślnie ustawiony na false.
bryan60

Odpowiedzi:

97

Zawiń ng-contentelement HTML, taki jak a, divaby uzyskać lokalne odwołanie do niego, a następnie powiąż ngIfwyrażenie z 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>`
bity pikseli
źródło
24
Czy nie ma innego sposobu? Ponieważ to jest brzydkie w porównaniu do automatów awaryjnych Aurelia
Astronaut
18
Jest bezpieczniejszy w użyciu ref.children.length. childNodes będą zawierać textwęzły, jeśli sformatujesz swój kod HTML spacjami lub nowymi wierszami, ale elementy podrzędne nadal będą puste.
parlament
5
Istnieje prośba o lepszą metodę w narzędziu do śledzenia problemów w Angular: github.com/angular/angular/issues/12530 (może warto dodać tam +1).
eppsilon
5
Miałem właśnie napisać, że to nie działa, dopóki nie zdałem sobie sprawy, że użyłem przykładu ref.childNodes.length == 0zamiast ref.children.length == 0. Pomogłoby wielu, gdybyś mógł edytować odpowiedź, aby była spójna. Łatwy błąd, żeby cię nie uderzyć. :)
Dustin Cleveland
3
Poszedłem za tym przykładem i dla mnie działa dobrze. Ale !ref.hasChildNodes()zamiast tego użyłemref.nativeElement.childNodes.length == 0
Sergey Dzyuba
34

Brakuje niektórych w odpowiedzi @pixelbits. Musimy sprawdzić nie tylko childrenwłaściwość, ponieważ wszelkie podziały wierszy lub spacje w szablonie nadrzędnym spowodują, że childrenelement z pustym tekstem \ podziałami wiersza. Lepiej to sprawdzić .innerHTMLi .trim()to.

Przykład pracy:

<span #ref><ng-content></ng-content></span>
<span *ngIf="!ref.innerHTML.trim()">
    Content if empty
</span>
lfoma
źródło
1
najlepsza odpowiedź dla mnie :)
Kamil Kiełczewski
Czy możemy użyć tej metody, aby sprawdzić, czy element potomny jest pusty i ukryć rodzica, jeśli tak? Próbowałem, bez powodzenia
Kavinda Jayakody
@KavindaJayakody Tak, to sprawdzi, czy element potomny jest pusty. Pokaż swój kod.
lfoma
* ngIf = "! ref.innerHTML.trim ()" Ta część spowoduje problemy z wydajnością. Trim jest O (n).
RandomCode
1
@RandomCode ma rację, funkcja będzie wywoływana wiele razy. Lepszym sposobem jest sprawdzenie element.children.length lub element.childnodes.length, w zależności od tego, o co chcesz zapytać.
Stefan Rein
31

EDYCJA 17.03.2020

Pure CSS (2 rozwiązania)

Zapewnia domyślną zawartość, jeśli nic nie jest rzutowane na zawartość ng-content.

Możliwe selektory:

  1. :only-childselektor. Zobacz ten post tutaj :: only-child Selector

    Ten wymaga mniej kodu / znaczników. Wsparcie od IE 9: Can I Use: only-child

  2. :emptyselektor. Przeczytaj dalej.

    Wsparcie z IE 9 i częściowo od 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;
}

Na wypadek, gdyby to nie działało

Należy pamiętać, że posiadanie co najmniej jednej spacji jest uważane za niepuste. Angular usuwa spacje, ale na wszelki wypadek, jeśli tak nie jest:

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

lub

<div class="wrapper"><ng-content select="my-component"></ng-content</div>
Stefan Rein
źródło
1
Jak dotąd było to najlepsze rozwiązanie w moim przypadku użycia. Nie pamiętałem, że mieliśmy emptypseudoklasę CSS
julianobrasil
@Stefan domyślna zawartość i tak zostanie wyrenderowana ... będzie tylko ukryta. Tak więc w przypadku złożonych treści domyślnych nie jest to najlepsze rozwiązanie. Czy to jest poprawne?
BossOz
1
@BossOz Masz rację. Zostanie on wyrenderowany (zostanie wywołany konstruktor itp., Jeśli masz komponent kątowy). To rozwiązanie sprawdza się tylko w przypadku głupich widoków. Jeśli masz złożoną logikę, obejmującą ładowanie lub takie rzeczy, moim zdaniem najlepszym rozwiązaniem jest napisanie dyrektywy strukturalnej, która może uzyskać szablon / klasę (jakkolwiek chcesz ją zaimplementować) i w zależności od logiki, wyrenderuj żądany komponent lub domyślny. Na przykład sekcja komentarzy jest zdecydowanie za mała. Ponownie komentuję inny przykład, który mamy w jednej z naszych aplikacji.
Stefan Rein,
Jednym ze sposobów byłoby posiadanie komponentu wybierającego ContentChildren za pomocą dyrektywy (zapytanie za pomocą dyrektywy DirectiveClass, ale użycie jako dyrektywy strukturalnej, więc nie ma wstępnego ładowania związanego z samym posiadaniem znaczników): <loader [data]="allDataWeNeed"> <desired-component *loadingRender><desired-component> <other-default-component *renderWhenEmptyResult></other-default-component> </loader>aw komponencie ładującym można wyświetlić postrzegane ładowanie wydajności, podczas gdy dane się ładują. Możesz napisać / rozwiązać to na różne sposoby. To jest jedna demonstracja, jak możesz to rozwiązać.
Stefan Rein,
21

Po wstrzyknięciu zawartości dodaj zmienną odniesienia:

<div #content>Some Content</div>

aw swojej klasie komponentu pobierz odwołanie do wstrzykniętej zawartości za pomocą @ContentChild ()

@ContentChild('content') content: ElementRef;

więc w szablonie komponentu możesz sprawdzić, czy zmienna zawartości ma wartość

<div>
  <ng-content></ng-content>
  <span *ngIf="!content">
    Display this if ng-content is empty!
  </span>    
</div> 
Lerner
źródło
To najczystsza i znacznie lepsza odpowiedź. Obsługuje interfejs API ContentChild po wyjęciu z pudełka. Nie mogę zrozumieć, dlaczego najlepsza odpowiedź otrzymała tak wiele głosów.
CarbonDry
10
OP zapytał, czy ngContent jest pusty - to znaczy <MyContainer></MyContainer>. Rozwiązanie przewiduje użytkownikom tworzenie sub-element pod MyContainer: <MyContainer><div #content></div></MyContainer>. Chociaż jest to możliwość, nie powiedziałbym, że jest to lepsze.
pixelbits
8

Wstrzyknij elementRef: ElementRefi sprawdź, czy elementRef.nativeElementnie ma dzieci. To może działać tylko z encapsulation: ViewEncapsulation.Native.

Owiń <ng-content>tag i sprawdź, czy ma dzieci. To nie działa z encapsulation: ViewEncapsulation.Native.

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

i sprawdź, czy ma dzieci

@ViewChild('contentWrapper') contentWrapper;

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

(nie testowany)

Günter Zöchbauer
źródło
3
Odrzuciłem to z powodu użycia @ViewChild. Chociaż „Legal”, ViewChild nie powinno być używane do uzyskiwania dostępu do węzłów podrzędnych w szablonie. Jest to antywzorzec projektowy dlatego, podczas gdy rodzice mogą wiedzieć o dzieciach, nie powinny one Para do nich, jak to zwykle prowadzi do patologicznej Coupling ; bardziej odpowiednią formą transportu danych lub obsługi wniosków jest wykorzystanie metodologii sterowanej zdarzeniami .
Cody
5
@Cody dzięki za komentarz do twojego głosu przeciwnego. Właściwie nie podążam za twoją argumentacją. Szablon i klasa komponentów to jedna jednostka - komponent. Nie rozumiem, dlaczego dostęp do szablonu z kodu miałby być antywzorem. Angular (2/4) nie ma prawie nic wspólnego z AngularJS poza tym, że oba to frameworki internetowe i nazwa.
Günter Zöchbauer
2
Gunter, masz dwie naprawdę dobre uwagi. Angular niekoniecznie musi mieć piętno na drogach AngularJS. Zaznaczam jednak, że pierwszy punkt (i te, które przedstawiłem powyżej) kieruje się w stronę filozoficznej rynny inżynierii. To powiedziawszy, stałem się kimś w rodzaju eksperta w dziedzinie Software Coupling i odradzałbym [przynajmniej intensywne] używanie @ViewChild. Dziękuję za zrównoważenie moich komentarzy - uważam, że oba nasze komentarze są tak samo warte rozważenia, jak inne.
Cody
2
@Cody być może Twoja silna opinia na temat @ViewChild()pochodzi od nazwy. „Dzieci” niekoniecznie są dziećmi, są po prostu deklaratywną częścią komponentu (tak jak ja to widzę). Jeśli instancje komponentów utworzone dla tego znacznika są dostępami, jest to oczywiście inna historia, ponieważ wymagałoby to wiedzy przynajmniej o ich publicznym interfejsie, a to faktycznie stworzyłoby ścisłe powiązanie. To bardzo interesująca dyskusja. Martwi mnie, że nie mam czasu na myślenie o takich sprawach, ponieważ przy takich ramach zbyt często marnuje się czas, aby w ogóle znaleźć sposób.
Günter Zöchbauer
3
Brak ujawnienia tego w API jest prawdziwym anty-wzorcem :-(
Simon_Weaver
5

Jeśli chcesz wyświetlić domyślną zawartość, dlaczego nie, po prostu użyj selektora „only-child” z css.

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

na przykład: HTML

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

css

.default-content:not(:only-child) {
   display: none;
}
ekosystem 31
źródło
Raczej sprytne rozwiązanie, jeśli nie chcesz zajmować się ViewChild i elementami w komponencie. Lubię to!
M'sieur Toph '
Zwróć uwagę, że treść transkludowania musi być węzłem HTML (nie tylko tekstem)
M'sieur Toph '
2

W Angular 10 nieco się to zmieniło. Użyłbyś:

<div #ref><ng-content></ng-content></div> 
<span *ngIf="ref.children.length == 0">
  Display this if ng-content is empty!
</span>
John Hamm
źródło
1

W moim przypadku muszę ukryć rodzica pustej zawartości ng:

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

Proste działanie CSS:

.wrapper {
  display: inline-block;

  &:empty {
    display: none;
  }
}
smg
źródło
1

Wdrożyłem rozwiązanie za pomocą dekoratora @ContentChildren , które jest w pewnym sensie podobne do odpowiedzi @ Lernera.

Według dokumentów ten dekorator:

Pobierz QueryList elementów lub dyrektyw z zawartości DOM. Za każdym razem, gdy element podrzędny jest dodawany, usuwany lub przenoszony, lista zapytań zostanie zaktualizowana, a obserwowane zmiany na liście zapytań będą emitować nową wartość.

Zatem niezbędnym kodem w komponencie nadrzędnym będzie:

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

W klasie komponentów:

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

Następnie w szablonie komponentów:

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

Pełny przykład : https://stackblitz.com/edit/angular-jjjdqb

Znalazłem to rozwiązanie zaimplementowane w komponentach kątowych, dla matSuffix, w polu formularza komponencie .

W sytuacji, gdy zawartość komponentu jest wstrzykiwana później, po zainicjowaniu aplikacji, możemy również skorzystać z implementacji reaktywnej, subskrybując changeszdarzenie 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();
  }

}

Pełny przykład : https://stackblitz.com/edit/angular-essvnq

andreivictor
źródło