Displaying News On The Home Page

We want to display the 3 most recent news on the home page.

First we need a Home component. Let’s initialize it properly.

Solution

We use the CLI:

ng generate component home

Then we add HomeComponent in entryComponents in the module.

We declare it as a view for the Plone Site type in AppComponent:

import { HomeComponent } from './home/home.component';

...

this.services.traverser.addView('view', 'Plone Site', HomeComponent);

We want this component to display the 3 most recent news. The resource service from @plone/restapi-angular provides a find method to do that.

Here is the HomeComponent implementation:

import { Component, OnInit } from '@angular/core';
import { ViewView } from '@plone/restapi-angular';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent extends ViewView implements OnInit {

  news: any[] = [];

  ngOnInit() {
    this.services.resource.find(
      { portal_type: 'News Item' },
      '/',
      {
        sort_on: 'created',
        sort_order: 'reverse',
        size: 3,
      },
    ).subscribe(res => {
      this.news = res.items;
    });
  }
}

We could display those news with a basic layout like this:

<ul>
  <li *ngFor="let item of news">
    <a [traverseTo]="item['@id']">{{ item.title }}</a>
  </li>
</ul>

Titles are not enough, it would be better to display images.

The find method returns “light” search results, with only few metadata.

By adding the fullobjects: true parameter, it will retrieve the actual News Item objects, including the image:

this.services.resource.find(
  { portal_type: 'News Item' },
  '/',
  {
    sort_on: 'created',
    sort_order: 'reverse',
    size: 3,
    fullobjects: true,
  },
)
<ul>
  <li *ngFor="let item of news">
    <a [traverseTo]="item['@id']">{{ item.title }}</a>
    <img [src]="item.image.download" />
  </li>
</ul>

It does work, but what about turning it into a nice slideshow?

First let’s implement the logic. We need to manage the currently displayed news, and we need the news to provide a state property set to 'active' or 'inactive'.

 export class HomeComponent extends ViewView implements OnInit {

   news: any[] = [];
   current = -1;

   ngOnInit() {
     this.services.resource.find(
      { portal_type: 'News Item' },
      '/',
      {
        sort_on: 'created',
        sort_order: 'reverse',
        size: 3,
        fullobjects: true,
      },
    ).subscribe(res => {
      res.items.map(item => {
        item.state = 'inactive';
        this.news.push(item);
      })
      this.current = 0;
      this.news[this.current].state = 'active';
    });
  }

  goTo(index) {
    this.news[this.current].state = 'inactive';
    if (index < 0) {
      index = this.news.length - 1;
    }
    if (index == this.news.length) {
      index = 0;
    }
    this.current = index;
    this.news[this.current].state = 'active';
  }
}

Now let’s try it with our basic layout:

<div *ngIf="current > -1">
  <a [traverseTo]="news[current]['@id']">{{ news[current].title }}</a>
  <img [src]="news[current].image.download" />
</div>
<span (click)="goTo(current+1)">Next</span>

Good, now let’s render it with animations.

We need to import the animation module in app.module.ts:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
...
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ...

We need to declare the states and transition in the component decorator:

import {
  trigger,
  state,
  style,
  animate,
  transition
} from '@angular/animations';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss'],
  animations: [
    trigger('flyInOut', [
      state('inactive', style({
        transform: 'translateX(-100%)'
      })),
      state('active', style({
        transform: 'translateX(0)'
      })),
      transition('inactive => active', [
        animate(200, style({ transform: 'translateX(0)' }))
      ]),
      transition('active => inactive', [
        animate(200, style({ transform: 'translateX(-100%)' }))
      ])
    ])
  ]
})

And we need update the markup in home.component.html:

<div class="col-md-12 slider">
  <div *ngFor="let item of news" class="slide"
    [@flyInOut]="item.state">
    <img [src]="item.image.download" />
    <div>
      <div class="title">
        <a [traverseTo]="item['@id']">{{ item.title }}</a>
      </div>
      <div class="description">
        <p>{{ item.description }}</p>
      </div>
    </div>
    <i class="next-news glyphicon glyphicon-chevron-right" (click)="goTo(current+1)"></i>
  </div>
</div>

… and the style in home.component.scss:

@import "../../variables.scss";

.slider {
  position: relative;
  padding: 0;
  height: 400px;
  overflow: hidden;
}
.slide {
  height: 300px;
  position: absolute;
  top: 0;
  width: 100%;
  img {
    width: 100%;
    height: auto
  }
  & > div {
    position: absolute;
    top: 60%;
    left: 66%;
  }
  .title, .description {
    text-transform: uppercase;
    text-decoration: none;
    color: white;
    background-color: $blue;
    padding: 1.5em;
  }
  a {
    color: white;
    font-weight: bold;
    font-size: 120%;
  }
  .next-news {
    color: white;
    position: absolute;
    font-weight: strong;
    right: 10px;
    top: 10px;
  }
}

And we are done!