diff --git a/projects/ngrx.io/content/marketing/events.html b/projects/ngrx.io/content/marketing/events.html
index 5f1756cec3..436c358dc7 100755
--- a/projects/ngrx.io/content/marketing/events.html
+++ b/projects/ngrx.io/content/marketing/events.html
@@ -2,73 +2,5 @@
Events
-Upcoming Events presenting about NgRx:
-
-
-
- Event |
- Location |
- Date |
-
-
-
-
- ngAtlanta |
- Atlanta, Georgia |
- January 9 - 12, 2019 |
-
-
- ng-India |
- Gurgaon, India |
- February 23, 2019 |
-
-
- ng-conf |
- Salt Lake City, Utah |
- May 1 - 3, 2019 |
-
-
-
-Past Events:
-
-
-
- Event |
- Location |
- Date |
-
-
-
-
- NgColombia |
- Medellín, Colombia |
- September 6 - 7th, 2018 |
-
-
- DevFestATL |
- Atlanta, GA |
- September 22nd, 2018 |
-
-
- Framework Summit |
- Park City, UT |
- September 22nd, 2018 |
-
-
- DevFestATL |
- Atlanta, GA |
- October 2 - 3rd, 2018 |
-
-
- AngularMix - Use the discount code "RYAN" or "ROBERTS" to receive $50 off your registration! |
- Orlando, FL |
- October 10 - 12th, 2018 |
-
-
- AngularConnect |
- Excel, London |
- November 6 - 7th, 2018 |
-
-
-
+
diff --git a/projects/ngrx.io/content/marketing/events.json b/projects/ngrx.io/content/marketing/events.json
new file mode 100644
index 0000000000..74e3b6c033
--- /dev/null
+++ b/projects/ngrx.io/content/marketing/events.json
@@ -0,0 +1,115 @@
+[
+ {
+ "name": "NgColombia",
+ "url": "http://www.ngcolombia.com",
+ "location": "Medellín, Colombia",
+ "startDate": "2018-09-06",
+ "endDate": "2018-09-07"
+ },
+ {
+ "name": "DevFestATL",
+ "url": "http://devfestatl.com",
+ "location": "Atlanta, Georgia",
+ "endDate": "2018-09-22"
+ },
+ {
+ "name": "Framework Summit",
+ "url": "http://frameworksummit.com",
+ "location": "Park City, Utah",
+ "endDate": "2018-09-22"
+ },
+ {
+ "name": "DevFestATL",
+ "url": "http://devfestatl.com",
+ "location": "Atlanta, Georgia",
+ "startDate": "2018-10-02",
+ "endDate": "2018-10-03"
+ },
+ {
+ "name": "AngularMix",
+ "url": "http://www.angularmix.com",
+ "location": "Orlando, Florida",
+ "startDate": "2018-10-10",
+ "endDate": "2018-10-12"
+ },
+ {
+ "name": "AngularConnect",
+ "url": "https://www.angularconnect.com",
+ "location": "Excel, London",
+ "startDate": "2018-11-06",
+ "endDate": "2018-11-07"
+ },
+ {
+ "name": "ngAtlanta",
+ "url": "http://ng-atl.org/",
+ "location": "Atlanta, Georgia",
+ "startDate": "2019-01-09",
+ "endDate": "2019-01-12"
+ },
+ {
+ "name": "ng-India",
+ "url": "https://www.ng-ind.com",
+ "location": "Gurgaon, India",
+ "endDate": "2019-02-23"
+ },
+ {
+ "name": "Open Source 101",
+ "url": "https://opensource101.com/",
+ "location": "Columbia, South Carolina",
+ "endDate": "2019-04-18"
+ },
+ {
+ "name": "NG-Conf",
+ "url": "https://www.ng-conf.org",
+ "location": "Salt Lake City, Utah",
+ "startDate": "2019-05-01",
+ "endDate": "2019-05-03"
+ },
+ {
+ "name": "ngVikings",
+ "url": "https://ngvikings.org/",
+ "location": "Copenhagen, Denmark",
+ "startDate": "2019-05-26",
+ "endDate": "2019-05-28"
+ },
+ {
+ "name": "REFACTR.TECH",
+ "url": "https://refactr.tech/",
+ "location": "Atlanta, Georgia",
+ "startDate": "2019-06-05",
+ "endDate": "2019-06-07"
+ },
+ {
+ "name": "AngularUP",
+ "url": "https://angular-up.com/",
+ "location": "Tel Aviv, Israel",
+ "endDate": "2019-06-12"
+ },
+ {
+ "name": "NG-MY",
+ "url": "https://ng-my.org/",
+ "location": "Kuala Lumpur, Malaysia",
+ "startDate": "2019-07-06",
+ "endDate": "2019-07-07"
+ },
+ {
+ "name": "ngDenver",
+ "url": "http://angulardenver.com/",
+ "location": "Denver, Colorado",
+ "startDate": "2019-08-01",
+ "endDate": "2019-08-02"
+ },
+ {
+ "name": "AngularConnect",
+ "url": "https://www.angularconnect.com/",
+ "location": "London, United Kingdom",
+ "startDate": "2019-09-19",
+ "endDate": "2019-09-20"
+ },
+ {
+ "name": "NG-Rome",
+ "url": "https://ngrome.io/",
+ "location": "Rome, Italy",
+ "endDate": "2019-10-07"
+ }
+]
diff --git a/projects/ngrx.io/src/app/custom-elements/element-registry.ts b/projects/ngrx.io/src/app/custom-elements/element-registry.ts
index d83e70a726..c969c3f1c9 100644
--- a/projects/ngrx.io/src/app/custom-elements/element-registry.ts
+++ b/projects/ngrx.io/src/app/custom-elements/element-registry.ts
@@ -17,6 +17,10 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
selector: 'aio-contributor-list',
loadChildren: './contributor/contributor-list.module#ContributorListModule',
},
+ {
+ selector: 'aio-event-list',
+ loadChildren: './events/event-list.module#EventListModule',
+ },
{
selector: 'aio-file-not-found-search',
loadChildren:
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.spec.ts b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.spec.ts
new file mode 100644
index 0000000000..5a9f284c53
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.spec.ts
@@ -0,0 +1,64 @@
+import { EventDateRangePipe } from './event-date-range.pipe';
+import { Event } from './event.model';
+
+describe('Pipe: Event Date Range', () => {
+ let pipe: EventDateRangePipe;
+
+ beforeEach(() => {
+ pipe = new EventDateRangePipe();
+ });
+
+ it('providing no startDate should format only the endDate in MM DD, YYYY', () => {
+ const event: Event = {
+ name: '',
+ url: '',
+ location: '',
+ endDate: new Date('2019-01-01')
+ };
+ expect(pipe.transform(event)).toBe('January 1, 2019');
+ });
+
+ it('providing the same startDate and endDate should format only one in MM DD, YYYY', () => {
+ const event: Event = {
+ name: '',
+ url: '',
+ location: '',
+ startDate: new Date('2019-01-01'),
+ endDate: new Date('2019-01-01')
+ };
+ expect(pipe.transform(event)).toBe('January 1, 2019');
+ });
+
+ it('providing different days in the same month and year should format in MM DD - DD, YYYY format', () => {
+ const event: Event = {
+ name: '',
+ url: '',
+ location: '',
+ startDate: new Date('2019-01-01'),
+ endDate: new Date('2019-01-02')
+ };
+ expect(pipe.transform(event)).toBe('January 1 - 2, 2019');
+ });
+
+ it('providing different days and months in the same year should format in MM DD - MM DD, YYYY format', () => {
+ const event: Event = {
+ name: '',
+ url: '',
+ location: '',
+ startDate: new Date('2019-01-01'),
+ endDate: new Date('2019-01-02')
+ };
+ expect(pipe.transform(event)).toBe('January 1 - 2, 2019');
+ });
+
+ it('providing different days, months, and years should format in MM DD, YYYY - MM DD, YYYY format', () => {
+ const event: Event = {
+ name: '',
+ url: '',
+ location: '',
+ startDate: new Date('2018-12-31'),
+ endDate: new Date('2019-01-01')
+ };
+ expect(pipe.transform(event)).toBe('December 31, 2018 - January 1, 2019');
+ });
+});
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.ts b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.ts
new file mode 100644
index 0000000000..f3d7076712
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.ts
@@ -0,0 +1,55 @@
+import { Pipe, PipeTransform } from '@angular/core';
+import { Event } from './event.model';
+
+/**
+ * Transforms the startDate and endDate for a given event into a dange range string.
+ * undefined until '01-01-2019' -> 'January 1, 2019'
+ * '01-01-2019' until '01-01-2019' -> 'January 1, 2019'
+ * '01-01-2019' until '01-02-2019' -> 'January 1 - 2, 2019'
+ * '01-28-2019' until '02-01-2019' -> 'January 28 - February 1, 2019'
+ * '12-31-2018' until '01-01-2019' -> 'December 31, 2018 - January 1, 2019'
+ */
+@Pipe({name: 'eventDateRange'})
+export class EventDateRangePipe implements PipeTransform {
+ transform(event: Event): string {
+ const startDate = event.startDate;
+ const endDate = event.endDate;
+ if (!startDate || startDate.getTime() === endDate.getTime()) {
+ return getDateString(endDate);
+ } else {
+ if (getMonth(startDate) === getMonth(endDate) && getYear(startDate) === getYear(endDate)) {
+ return getMonth(startDate)
+ + ' ' + getDay(startDate) + ' - ' + getDay(endDate)
+ + ', ' + getYear(startDate);
+ } else if (getYear(startDate) === getYear(endDate)) {
+ return getMonth(startDate)
+ + ' ' + getDay(startDate)
+ + ' - ' + getMonth(endDate)
+ + ' ' + getDay(endDate)
+ + ', ' + getYear(startDate);
+ } else {
+ return getDateString(startDate)
+ + ' - ' + getDateString(endDate);
+ }
+ }
+ }
+}
+
+const getDay = (date: Date) => date.getUTCDate();
+const getMonth = (date: Date) => months[date.getUTCMonth()];
+const getYear = (date: Date) => date.getUTCFullYear();
+const getDateString = (date: Date) => getMonth(date) + ' ' + getDay(date) + ', ' + getYear(date);
+const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December'
+];
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-list.component.spec.ts b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.spec.ts
new file mode 100644
index 0000000000..3c6a39c13a
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.spec.ts
@@ -0,0 +1,57 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { EventListComponent } from './event-list.component';
+import { of } from 'rxjs';
+import { EventService } from './event.service';
+import { Event } from './event.model';
+import { EventDateRangePipe } from './event-date-range.pipe';
+
+const mockUpcomingEvents: Event[] = [
+ {
+ name: 'NG-Rome',
+ url: 'https://ngrome.io/',
+ location: 'Rome, Italy',
+ endDate: new Date('2019-10-07')
+ }
+];
+
+const mockPastEvents: Event[] = [
+ {
+ name: 'DevFestATL',
+ url: 'http://devfestatl.com',
+ location: 'Atlanta, Georgia',
+ endDate: new Date('2018-09-22')
+ }
+];
+
+class TestEventService {
+ upcomingEvents$ = of(mockUpcomingEvents);
+ pastEvents$ = of(mockPastEvents);
+}
+
+describe('Event List Component', () => {
+
+ let fixture: ComponentFixture;
+ let component: EventListComponent;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [ EventListComponent, EventDateRangePipe ],
+ providers: [{ provide: EventService, useClass: TestEventService }]
+ });
+
+ fixture = TestBed.createComponent(EventListComponent);
+ component = fixture.componentInstance;
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should set the upcomingEvents', () => {
+ component.upcomingEvents$.subscribe(events => expect(events).toEqual(mockUpcomingEvents));
+ });
+
+ it('should set the pastEvents', () => {
+ component.pastEvents$.subscribe(events => expect(events).toEqual(mockPastEvents));
+ });
+});
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-list.component.ts b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.ts
new file mode 100644
index 0000000000..08e5fa117c
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.ts
@@ -0,0 +1,50 @@
+import { Component } from '@angular/core';
+import { Event } from './event.model';
+import { EventService } from './event.service';
+import { Observable } from 'rxjs';
+
+@Component({
+ selector: `aio-event-list`,
+ template: `
+ Upcoming Events presenting about NgRx:
+
+
+
+ Event |
+ Location |
+ Date |
+
+
+
+
+ {{upcomingEvent.name}} |
+ {{upcomingEvent.location}} |
+ {{upcomingEvent | eventDateRange}} |
+
+
+
+ Past Events:
+
+
+
+ Event |
+ Location |
+ Date |
+
+
+
+
+ {{pastEvent.name}} |
+ {{pastEvent.location}} |
+ {{pastEvent | eventDateRange}} |
+
+
+
+`
+})
+export class EventListComponent {
+ upcomingEvents$: Observable = this.eventService.upcomingEvents$;
+ pastEvents$: Observable = this.eventService.pastEvents$;
+
+ constructor(private eventService: EventService) { }
+}
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-list.module.ts b/projects/ngrx.io/src/app/custom-elements/events/event-list.module.ts
new file mode 100644
index 0000000000..0a5e8cb060
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event-list.module.ts
@@ -0,0 +1,16 @@
+import { NgModule, Type } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { WithCustomElementComponent } from '../element-registry';
+import { EventListComponent } from './event-list.component';
+import { EventService } from './event.service';
+import { EventDateRangePipe } from './event-date-range.pipe';
+
+@NgModule({
+ imports: [ CommonModule ],
+ declarations: [ EventListComponent, EventDateRangePipe ],
+ entryComponents: [ EventListComponent ],
+ providers: [ EventService ]
+})
+export class EventListModule implements WithCustomElementComponent {
+ customElementComponent: Type = EventListComponent;
+}
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event.model.ts b/projects/ngrx.io/src/app/custom-elements/events/event.model.ts
new file mode 100644
index 0000000000..6a4599f67f
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event.model.ts
@@ -0,0 +1,15 @@
+export interface EventResponse {
+ name: string;
+ url: string;
+ location: string;
+ startDate?: string;
+ endDate: string;
+}
+
+export interface Event {
+ name: string;
+ url: string;
+ location: string;
+ startDate?: Date;
+ endDate: Date;
+}
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event.service.spec.ts b/projects/ngrx.io/src/app/custom-elements/events/event.service.spec.ts
new file mode 100644
index 0000000000..ed51276211
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event.service.spec.ts
@@ -0,0 +1,116 @@
+import {
+ HttpClientTestingModule,
+ HttpTestingController,
+} from '@angular/common/http/testing';
+import { Injector } from '@angular/core';
+import { TestBed } from '@angular/core/testing';
+import { EventService } from './event.service';
+import { Event, EventResponse } from './event.model';
+
+describe('Event Service', () => {
+ let injector: Injector;
+ let eventService: EventService;
+ let httpMock: HttpTestingController;
+
+ beforeEach(() => {
+ injector = TestBed.configureTestingModule({
+ imports: [HttpClientTestingModule],
+ providers: [EventService],
+ });
+
+ eventService = injector.get(EventService);
+ eventService.currentDate = new Date('2019-01-02');
+ httpMock = injector.get(HttpTestingController);
+ });
+
+ afterEach(() => httpMock.verify());
+
+ it('should make a single connection to the server', () => {
+ const req = httpMock.expectOne({});
+ expect(req.request.url).toBe('generated/events.json');
+ });
+
+ describe('events', () => {
+ let upcomingEvents: Event[];
+ let pastEvents: Event[];
+ let testData: EventResponse[];
+
+ beforeEach(() => {
+ testData = getTestEventResponse();
+ httpMock.expectOne({}).flush(testData);
+ eventService.upcomingEvents$.subscribe(results => (upcomingEvents = results));
+ eventService.pastEvents$.subscribe(results => (pastEvents = results));
+ });
+
+ it('upcomingEvents$ observable should complete', () => {
+ let completed = false;
+ eventService.upcomingEvents$.subscribe(
+ undefined,
+ undefined,
+ () => (completed = true)
+ );
+ expect(completed).toBe(true, 'observable completed');
+ });
+
+ it('pastEvents$ observable should complete', () => {
+ let completed = false;
+ eventService.pastEvents$.subscribe(
+ undefined,
+ undefined,
+ () => (completed = true)
+ );
+ expect(completed).toBe(true, 'observable completed');
+ });
+
+ describe('upcoming events', () => {
+ it('should emit', () => {
+ expect(upcomingEvents.length).toEqual(3)
+ });
+ });
+
+ describe('past events', () => {
+ it('should emit', () => {
+ expect(pastEvents.length).toEqual(2)
+ });
+ });
+ });
+});
+
+function getTestEventResponse(): EventResponse[] {
+ return [
+ {
+ name: 'conf1',
+ url: '',
+ location: '',
+ startDate: '2018-06-28',
+ endDate: '2018-07-01'
+ },
+ {
+ name: 'conf2',
+ url: '',
+ location: '',
+ startDate: '2018-12-25',
+ endDate: '2019-01-01'
+ },
+ {
+ name: 'conf3',
+ url: '',
+ location: '',
+ startDate: '2019-01-01',
+ endDate: '2019-01-02'
+ },
+ {
+ name: 'conf4',
+ url: '',
+ location: '',
+ endDate: '2019-04-01'
+ },
+ {
+ name: 'conf5',
+ url: '',
+ location: '',
+ startDate: '2019-04-02',
+ endDate: '2019-04-02'
+ },
+ ];
+}
diff --git a/projects/ngrx.io/src/app/custom-elements/events/event.service.ts b/projects/ngrx.io/src/app/custom-elements/events/event.service.ts
new file mode 100644
index 0000000000..dccf0394b9
--- /dev/null
+++ b/projects/ngrx.io/src/app/custom-elements/events/event.service.ts
@@ -0,0 +1,55 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { ConnectableObservable, Observable } from 'rxjs';
+import { publishLast, map } from 'rxjs/operators';
+import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
+import { EventResponse, Event } from './event.model';
+
+const eventsPath = CONTENT_URL_PREFIX + 'events.json';
+
+@Injectable()
+export class EventService {
+ currentDate: Date;
+ private events$: Observable;
+ upcomingEvents$: Observable;
+ pastEvents$: Observable;
+
+ constructor(private http: HttpClient) {
+ const now = new Date();
+ // Compare soley on UTC date, without factoring in time.
+ this.currentDate = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
+ this.events$ = this.getEvents();
+ this.upcomingEvents$ = this.events$.pipe(
+ map(events =>
+ events.filter(event => event.endDate >= this.currentDate)
+ )
+ );
+ this.pastEvents$ = this.events$.pipe(
+ map(events =>
+ events.filter(event => event.endDate < this.currentDate)
+ )
+ );
+ }
+
+ /**
+ * Fetch Event JSON from file and return an Observable that emits an Event array.
+ */
+ private getEvents(): Observable {
+ const events = this.http.get(eventsPath).pipe(
+ map(eventResponses =>
+ eventResponses.map(eventResponse => {
+ const event: Event = {
+ ...eventResponse,
+ startDate: eventResponse.startDate ? new Date(eventResponse.startDate) : undefined,
+ endDate: new Date(eventResponse.endDate)
+ };
+ return event;
+ })
+ ),
+ publishLast(),
+ );
+
+ (events as ConnectableObservable).connect();
+ return events;
+ };
+}
diff --git a/projects/ngrx.io/tools/transforms/angular-content-package/index.js b/projects/ngrx.io/tools/transforms/angular-content-package/index.js
index b0635ae198..973e2d7b8f 100644
--- a/projects/ngrx.io/tools/transforms/angular-content-package/index.js
+++ b/projects/ngrx.io/tools/transforms/angular-content-package/index.js
@@ -82,6 +82,11 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
include: CONTENTS_PATH + '/marketing/resources.json',
fileReader: 'jsonFileReader'
},
+ {
+ basePath: CONTENTS_PATH,
+ include: CONTENTS_PATH + '/marketing/events.json',
+ fileReader: 'jsonFileReader'
+ },
]);
collectExamples.exampleFolders.push('examples');
@@ -110,7 +115,8 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
{docTypes: ['navigation-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'},
{docTypes: ['contributors-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'},
{docTypes: ['announcements-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'},
- {docTypes: ['resources-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}
+ {docTypes: ['resources-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'},
+ {docTypes: ['events-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}
]);
})