The AngularPortfolioMgr project has been extended to show 2 financial news feeds. The news are from Yahoo and Nbc in RSS feed format. To load the news feeds from the provider the Rome library is used.

Backend

The newsfeeds are managed by the NewsFeedService:

@Service public class NewsFeedService { private static final Logger LOGGER = LoggerFactory.getLogger(NewsFeedService.class); private final NewsFeedClient newsFeedClient; private volatile Optional<SyndFeed> yahooNewsFeedOptional = Optional.empty(); private volatile Optional<SyndFeed> cnbcFinanceNewsFeedOptional = Optional.empty(); public NewsFeedService(NewsFeedClient newsFeedClient) { this.newsFeedClient = newsFeedClient; } @Async public void updateYahooNewsFeed() { var start = Instant.now(); this.yahooNewsFeedOptional = Optional.ofNullable(this.newsFeedClient.importYahooNewsFeed()); LOGGER.info("YahooFinancial news imported in: {}ms", Instant.now().toEpochMilli() - start.toEpochMilli()); } @Async public void updateCnbcFinanceNewsFeed() { var start = Instant.now(); this.cnbcFinanceNewsFeedOptional = Optional.ofNullable(this.newsFeedClient.importCnbcFinanceNewsFeed()); LOGGER.info("Cnbc news imported in: {}ms", Instant.now().toEpochMilli() - start.toEpochMilli()); } public List<SyndEntry> getYahooNewsFeed() { return this.yahooNewsFeedOptional.stream().flatMap(myFeed -> myFeed.getEntries().stream()).map(myEntry -> { myEntry.getForeignMarkup().clear(); return myEntry; }).toList(); } public List<SyndEntry> getCnbcFinanceNewsFeed() { return this.cnbcFinanceNewsFeedOptional.stream().flatMap(myFeed -> myFeed.getEntries().stream()).map(myEntry -> { myEntry.getForeignMarkup().clear(); return myEntry; }).toList(); } }

The ‘yahooNewsFeedOptional’ and the ‘cnbcFinanceNewsFeedOptional’ are volatile to ensure the values are set atomically, because the references could be invalid otherwise.

The methods ‘updateYahooNewsFeed()’ and ‘updateCnbcFinanceNewsFeed()’ have the ‘@Async’ annotation to make sure that a problem with the requests has no impact on the program flow. They use the ‘newsFeedClient’ to request the new news lists. If the requests fail the optionals are set to empty.

To get the news feeds the methods ‘getYahooNewsFeed()’ and ‘getCnbcFinanceNewsFeed()’ are used. The optionals are used to create the ‘List<SyndEntry>’ of the news feeds. The optionals decouples the news feeds updates from the requests of the news feeds.

The NewsFeedClient request the news feed of the resources:

@Component public class NewsFeedConnector implements NewsFeedClient { public static final String YAHOO_FINANCE_URL = "https://finance.yahoo.com/news/rssindex"; public static final String CNBC_FINANCE_URL = "https://search.cnbc.com/rs/search/combinedcms/view.xml? partnerId=wrss01&id=10000664"; private static final Logger LOGGER = LoggerFactory.getLogger(NewsFeedConnector.class); @Override public SyndFeed importYahooNewsFeed() { return this.importNewsFeed(YAHOO_FINANCE_URL); } @Override public SyndFeed importCnbcFinanceNewsFeed() { return this.importNewsFeed(CNBC_FINANCE_URL); } private SyndFeed importNewsFeed(String url) { SyndFeed feed = null; try { SyndFeedInput input = new SyndFeedInput(); feed = input.build(new XmlReader( URI.create(url).toURL().openStream())); } catch (Exception e) { LOGGER.error(String.format("Feed import failed. url: %s", url),e); } return feed; } }

The ‘importNewsFeed(…)’ returns the ‘SyndFeed’ of the news feeds. The ‘URI’ class is used to create a stream of the url. The stream is feed to the ‘XmlReader(…)’ to get the xml object tree and that is feed to the ‘SyndFeedInput()’ to create the ‘SyndFeed’. If the request fails the exception is caught and null is returned.

The news feed updates are triggered in the ‘OnStart.startupDone()’ method and in the CronJobService:

@Scheduled(cron = "0 */15 * * * ?") @Order(2) public void updateNewsFeeds() { this.newsFeedService.updateCnbcFinanceNewsFeed(); this.newsFeedService.updateYahooNewsFeed(); }

The ‘@Scheduled’ annotation calls the ‘updateNewsFeeds()’ method every 15 minutes. The update methods are called in parallel because they have the ‘@Async’ annotation. The ‘startupDone()’ method calls the update methods as soon as the application startup is finished to ensure the news feeds are available after startup soon.

The controller for the news feeds can be found in the NewsFeedController class and contains 2 simple get mappings for the feeds.

Frontend

The frontend requests the news feeds with the NewsService:

@Injectable() export class NewsService { constructor(private httpClient: HttpClient) {} getYahooNews(): Observable<NewsItem[]> { return this.httpClient.get<NewsItem[]>("/rest/newsfeed/yahoo-finance"); } getCnbcFinanceNews(): Observable<NewsItem[]> { return this.httpClient.get<NewsItem[]>("/rest/newsfeed/cnbc-finance"); } }

The ‘NewsService’ contains the methods ‘getCnbcFinanceNews()’ and ‘getYahooNews()’ to request the ‘NewsItem’ arrays from the backend and return them in an observable.

The PortfolioChartsComponent has the ‘ngOnInit()’ method that loads the ‘NewsItem’ arrays and provides them to the NewsListComponents:

ngOnInit(): void { this.newsService.getCnbcFinanceNews() .subscribe((result) => (this.cnbcFinanceNews = result)); this.newsService.getYahooNews() .subscribe((result) => (this.yahooFinanceNews = result)); ... }

The template of the component provides the ‘NewsItem’ arrays to the NewsListComponents:

<mat-tab label="Yahoo Finance" i18n-label="@@portfolioChartsYahooFinance"> <app-news-list [newsItems]="yahooFinanceNews"></app-news-list> </mat-tab> <mat-tab label="Cnbc Finance" i18n-label="@@portfolioChartsCnbcFinance"> <app-news-list [newsItems]="cnbcFinanceNews"></app-news-list> </mat-tab>

The template of the NewsListComponent displays the ‘NewsItem’ array:

<div class="container"> @for (item of newsItems; track item) { <div class="news-item"> <h2>{{ item.title }}</h2> <div>{{ item.publishedDate | date: "dd-MM-yyyy" }}</div> <a [href]="item.link" target="_blank">Open article</a> </div> } </div>

The ‘NewsItem’ array elements are displayed with title, publishedDate and the link to open the article. The ‘target=”_blank” opens the url in a new tab of the browser to keep the angular application open.

Conclusion

The news feeds support is good and can provide value to the users of the application. The Rome library makes the implementation easy and Spring provides the infrastructure to implement the client and the controller. With Angular the implementing the news feed display was easy too. RSS has more than a decade of history but it is still very useful for adding news articles to an application. Due to the links provided by the RSS feeds the original article is opened and the news sources have an incentive to provide such feeds.