Search code examples
javascriptcssangularangular-materialexpand

Using CSS or JS for Click-Expandable Text on Headlines Section of Angular App


I have a section on the homepage of my Angular app where I'm displaying some stories with a headline and about two lines of opening content. What I'd like to do is provide that, with an "Expand" button at the end of the opening two lines that, when clicked, will cause the text space to expand to allow for the remainder of the text. I'd also want it toggle-able, so that the text can again be minimized by clicking on the same button.

Is there a way I can accomplish this with CSS/HTML alone? Or this best done via JavaScript -- or a combination of the two? I'm also wondering if Angular material has something out of the box to accomplish this kind of UI treatment. Perhaps an Expansion Panel (https://material.angular.io/components/expansion/overview) could work? What does Google use for their own click-expandable stories in Google news (https://news.google.com)?

Overall, I'm looking for an elegant, simple solution that works with modern browsers. Also, note that this will be dynamic content, so it needs to be able to work by calculating the number of characters, or something like that, rather than by grouping the info in different div elements ahead of time.


Solution

  • Since you're using Angular you should do this the "angular" way.

    We'll be using CSS and Angular Animations.

    Working Example


    Explanation:

    Our component will be called app-card and by clicking on its header we'll show/hide the full contents of the card's "body".

    card.component.html

    <div class="card-container">
    
      <div class="card-header" (click)="toggleFold()">
        I am the head of the card
      </div>
    
      <div class="card-body" [@panelState]="folded">
        <ng-content></ng-content>
      </div>
    
    </div>
    

    Key parts to note are the toggleFold() function that happens when we click the cards header, and the @panelState that binds the current state of our card-body depending on the folded property.

    card.component.ts

    import { Component, OnInit } from '@angular/core';
    import { animate, state, style, transition, trigger } from '@angular/animations';
    
    @Component({
      selector: 'app-card',
      templateUrl: './card.component.html',
      styleUrls: ['./card.component.css'],
      animations : [
        // Here we are defining what are the states our panel can be in 
        // and the style each state corresponds to.
        trigger('panelState', [
          state('closed', style({ height: '32px', overflow: 'hidden' })),
          state('open', style({ height: '*' })),
          transition('closed <=> open', animate('300ms ease-in-out')),
        ]),
      ],
    })
    export class CardComponent {
      folded = 'closed';
    
      // toggleFold function simply changes our folded property
      // between "open" and "closed"
      toggleFold(){
        this.folded = this.folded === 'open' ? 'closed' : 'open';
      }
    }
    

    NOTE:

    • In order to use angular animations you need to import the "BrowserAnimationsModule" from "@angular/platform-browser/animations" into your app.module.ts

    app.module.ts

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    
    import { AppComponent } from './app.component';
    import { CardComponent } from './card/card.component';
    
    @NgModule({
      imports:      [ 
        BrowserModule, 
        BrowserAnimationsModule, // <<
      ],
      declarations: [ 
        AppComponent, 
        CardComponent,
      ],
      bootstrap: [ AppComponent ]
    })
    export class AppModule { }