Skip to content

Commit

Permalink
feat: add disqus comments to the posts component
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts committed Jun 28, 2020
1 parent e48bf59 commit a6e4fba
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 52 deletions.
2 changes: 0 additions & 2 deletions content/pages/talks.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ Below are a list of my conference talks, and podcast appearances.

### Upcoming



### Previous

#### 2020
Expand Down
8 changes: 6 additions & 2 deletions content/posts/2020-05-14-angular-unfiltered-001.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ publishedDate: '2020-05-14 02:00 PM CST'

In the first episode of Angular Unfiltered, I had a rather impromptu chat with my friend [Mike Ryan](https://twitter.com/MikeRyanDev). The discussion was prompted by a tweet asking which Angular Forms library is best:

<blockquote class="twitter-tweet center"><p lang="en" dir="ltr">Just wrapping up the section on Template-Driven forms for my new <a href="https://twitter.com/angular?ref_src=twsrc%5Etfw">@angular</a> course on <a href="https://twitter.com/GoThinkster?ref_src=twsrc%5Etfw">@GoThinkster</a>. It&#39;s like 90 minutes long....(although about a third of that is hands on challenges)<br><br>So it&#39;s grudge match time. In Angular which Forms is best?</p>&mdash; Joe Eames #blacklivesmatter (@josepheames) <a href="https://twitter.com/josepheames/status/1260980002092351488?ref_src=twsrc%5Etfw">May 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<div class="center">
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Just wrapping up the section on Template-Driven forms for my new <a href="https://twitter.com/angular?ref_src=twsrc%5Etfw">@angular</a> course on <a href="https://twitter.com/GoThinkster?ref_src=twsrc%5Etfw">@GoThinkster</a>. It&#39;s like 90 minutes long....(although about a third of that is hands on challenges)<br><br>So it&#39;s grudge match time. In Angular which Forms is best?</p>&mdash; Joe Eames #blacklivesmatter (@josepheames) <a href="https://twitter.com/josepheames/status/1260980002092351488?ref_src=twsrc%5Etfw">May 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>

And Mike's response:

<blockquote class="twitter-tweet center"><p lang="en" dir="ltr">Both kind of make me sad.</p>&mdash; Mike Ryan (@MikeRyanDev) <a href="https://twitter.com/MikeRyanDev/status/1260980682387599360?ref_src=twsrc%5Etfw">May 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<div>
<blockquote class="twitter-tweet center"><p lang="en" dir="ltr">Both kind of make me sad.</p>&mdash; Mike Ryan (@MikeRyanDev) <a href="https://twitter.com/MikeRyanDev/status/1260980682387599360?ref_src=twsrc%5Etfw">May 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>

We talk about the state of template-driven, and reactive forms in Angular, what improvements could be made, and the possibility of a building a new forms library.

Expand Down
49 changes: 22 additions & 27 deletions content/posts/2020-05-14-mixing-action-styles-ngrx.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,20 @@ publishedDate: '2020-05-14 02:00 PM CST'
<img src="/assets/posts/franck-v-miWGZ02CLKI-unsplash.jpg" width="100%"/>
</a>


Prior to version 8 of the NgRx platform, actions were created using enums, classes, and union types. Many people thought this approach was too noisy, and refer to it as [boilerplate](https://www.youtube.com/watch?v=t3jx0EC-Y3c&t=325s) 😉. In [version 8](https://medium.com/ngrx/announcing-ngrx-version-8-ngrx-data-create-functions-runtime-checks-and-mock-selectors-a44fac112627), we introduced the new creator functions for actions, reducers, and effects. Recently, the question was asked, if you have an existing application, can you use the old syntax with the new syntax? Let's mix things up.

<iframe src="https://giphy.com/embed/RLPtfIGR5YoKQhHUSA" width="100%" height="270" frameBorder="0" class="giphy-embed" allowFullScreen></iframe><p><a href="https://giphy.com/gifs/rastamouse-cooking-masterchef-baking-RLPtfIGR5YoKQhHUSA">via GIPHY</a></p>

For this example, I'll start with the [counter example](https://ngrx.io/generated/live-examples/store/stackblitz.html) using StackBlitz from the [NgRx docs](https://ngrx.io).


### Using with Reducers

To separate the two action styles, put them in different files. In the `counter.actions.ts` file, there is an action creator.

```ts
import { createAction } from '@ngrx/store';

export const increment = createAction(
'[Counter Component] Increment'
);
export const increment = createAction('[Counter Component] Increment');
```

Create a new file named `legacy-counter.actions.ts`. In the actions file, define an Increment action using the action class syntax.
Expand All @@ -38,7 +34,7 @@ Create a new file named `legacy-counter.actions.ts`. In the actions file, define
import { Action } from '@ngrx/store';

export enum CounterActionTypes {
Increment = '[Counter Component] Legacy Increment'
Increment = '[Counter Component] Legacy Increment',
}

export class Increment {
Expand All @@ -50,7 +46,6 @@ export type Union = Increment;

The action type is different than the modern action using the creator function. In the counter.reducer.ts file, import the legacy actions. Before mixing the types of the legacy and modern syntax together, you need to create a union type of the two. The `@ngrx/store` package contains a helper utility function named `union` for returning the types of a dictionary of creator functions.


- Import the actions from `counter.actions.ts` using module import syntax
- Pass the object to the union function using the spread operator

Expand All @@ -65,9 +60,7 @@ const CounterActionsUnion = union({...CounterActions});
This returns you the return types of the action creators. You already have an existing union of legacy counter actions, so you create a superset of the unions.

```ts
type Actions =
| LegacyCounterActions.Union
| typeof CounterActionsUnion;
type Actions = LegacyCounterActions.Union | typeof CounterActionsUnion;
```

The reducer creation function handles action creators, but you still need a way to handle action classes. Use a simple switch case to handle this scenario. The switch case handles your legacy actions, and the default uses the created reducer function.
Expand All @@ -81,18 +74,17 @@ export const initialState = 0;

type State = number;

const counterReducer = createReducer(initialState,
on(CounterActions.increment, state => state + 1)
const counterReducer = createReducer(
initialState,
on(CounterActions.increment, (state) => state + 1)
);

const CounterActionsUnion = union({...CounterActions});
const CounterActionsUnion = union({ ...CounterActions });

type Actions =
| LegacyCounterActions.Union
| typeof CounterActionsUnion;
type Actions = LegacyCounterActions.Union | typeof CounterActionsUnion;

export function reducer(state: State | undefined, action: Actions) {
switch(action.type) {
switch (action.type) {
case LegacyCounterActions.CounterActionTypes.Increment:
return state + 1;
default:
Expand Down Expand Up @@ -130,15 +122,18 @@ import * as CounterActions from './counter.actions';

@Injectable()
export class CounterEffects {
increment$ = createEffect(() => {
return this.actions$.pipe(
ofType(
LegacyCounterActions.CounterActionTypes.Increment,
CounterActions.increment
),
tap(count => console.log('incremented'))
)
}, { dispatch: false });
increment$ = createEffect(
() => {
return this.actions$.pipe(
ofType(
LegacyCounterActions.CounterActionTypes.Increment,
CounterActions.increment
),
tap((count) => console.log('incremented'))
);
},
{ dispatch: false }
);
constructor(private actions$: Actions) {}
}
```
Expand All @@ -147,4 +142,4 @@ The `ofType` operator takes multiple actions, and knows how to distinguish each

That's It! To see a working example, see the completed [StackBlitz](https://stackblitz.com/edit/ngrx-mix-actions?embed=1&file=src/app/my-counter/my-counter.component.ts).

Follow me on [Twitter](https://twitter.com/brandontroberts), and [Twitch](https://twitch.tv/brandontroberts). If you like this content, consider [sponsoring me on GitHub](https://github.com/sponsors/brandonroberts).
Follow me on [Twitter](https://twitter.com/brandontroberts), and [Twitch](https://twitch.tv/brandontroberts). If you like this content, consider [sponsoring me on GitHub](https://github.com/sponsors/brandonroberts).
8 changes: 7 additions & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ import { map } from 'rxjs/operators';
<img src="assets/images/logos/github-icon.svg" />
</a>
<iframe src="https://github.com/sponsors/brandonroberts/button" title="Sponsor brandonroberts" height="35" width="107" style="border: 0;margin-left: 16px;"></iframe>
<iframe
src="https://github.com/sponsors/brandonroberts/button"
title="Sponsor brandonroberts"
height="35"
width="107"
style="border: 0;margin-left: 16px;"
></iframe>
</div>
</mat-toolbar>
Expand Down
32 changes: 14 additions & 18 deletions src/app/blog/blog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,21 @@ import { map } from 'rxjs/operators';
],
})
export class BlogComponent {
posts$: Observable<ScullyRoute[]>;
posts$ = this.routesService.available$.pipe(
map((routes) => routes.filter((route) => route.route.startsWith('/blog'))),
map((filteredRoutes) =>
filteredRoutes
.slice()
.sort((a, b) =>
new Date(a.publishedDate).getTime() >
new Date(b.publishedDate).getTime()
? -1
: 0
)
)
);

constructor(private routesService: ScullyRoutesService) {
this.posts$ = this.routesService.available$.pipe(
map((routes) =>
routes.filter((route) => route.route.startsWith('/blog'))
),
map((filteredRoutes) =>
filteredRoutes
.slice()
.sort((a, b) =>
new Date(a.publishedDate).getTime() >
new Date(b.publishedDate).getTime()
? -1
: 0
)
)
);
}
constructor(private routesService: ScullyRoutesService) {}
}

@NgModule({
Expand Down
50 changes: 50 additions & 0 deletions src/app/blog/comments.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
Component,
NgModule,
Renderer2,
ElementRef,
AfterViewInit,
} from '@angular/core';
import { ScullyRoutesService } from '@scullyio/ng-lib';

import { Router } from '@angular/router';
import { first } from 'rxjs/operators';

import { CommentsService } from './comments.service';

@Component({
selector: 'app-post-comments',
template: ` <div id="{{ containerId }}"></div> `,
styles: [''],
})
export class PostCommentsComponent implements AfterViewInit {
containerId = this.commentsService.containerId;

constructor(
private renderer: Renderer2,
private el: ElementRef,
private router: Router,
private routeService: ScullyRoutesService,
private commentsService: CommentsService
) {}

ngAfterViewInit() {
this.routeService
.getCurrent()
.pipe(first())
.subscribe((route) => {
const config = {
url: this.router.url,
title: `Brandon Roberts - ${route.title}`,
};

this.commentsService.initialize(config, this.renderer, this.el);
});
}
}

@NgModule({
declarations: [PostCommentsComponent],
exports: [PostCommentsComponent],
})
export class PostCommentsComponentModule {}
111 changes: 111 additions & 0 deletions src/app/blog/comments.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
Injectable,
InjectionToken,
Inject,
ElementRef,
Renderer2,
} from '@angular/core';
import { environment } from 'src/environments/environment';

export interface DisqusConfig {
url: string;
identifier: string;
title: string;
}

export interface Disqus {
reset: (config: object) => void;
}

export type WindowDisqus = Window & {
DISQUS: Disqus;
disqus_config: () => void;
disqus_container_id: string;
};

export const WINDOW_TOKEN = new InjectionToken<WindowDisqus>('Window', {
providedIn: 'root',
factory: () => {
return window as any;
},
});

export const DISQUS_TOKEN = new InjectionToken<Disqus>('DISQUS', {
providedIn: 'root',
factory: () => {
return (window as any).DISQUS;
},
});

@Injectable({
providedIn: 'root',
})
export class CommentsService {
readonly containerId = 'comments_thread';

constructor(
@Inject(WINDOW_TOKEN) private window: WindowDisqus,
@Inject(DISQUS_TOKEN) private disqus: Disqus
) {}

initialize(
page: { url: string; title: string },
renderer: Renderer2,
element: ElementRef
) {
this.window.disqus_container_id = this.containerId;

const config: DisqusConfig = {
url: environment.disqusConfig.url,
identifier: page.url,
title: page.title,
};

if (!this.initialized()) {
this.initializeComments(config, renderer, element);
} else {
this.reset(config);
}
}

reset(page: DisqusConfig) {
const config = this.getConfig(page);

this.disqus.reset({
reload: true,
config,
});
}

private initialized() {
return !!this.disqus;
}

private initializeComments(
page: DisqusConfig,
renderer: Renderer2,
element: ElementRef
) {
this.getConfig(page);

const disqusScript = renderer.createElement('script');
disqusScript.src = `//${environment.disqusConfig.shortname}.disqus.com/embed.js`;
disqusScript.async = true;
disqusScript.type = 'text/javascript';

renderer.setAttribute(
disqusScript,
'data-timestamp',
new Date().getTime().toString()
);
renderer.appendChild(element.nativeElement, disqusScript);
}

private getConfig(page: DisqusConfig) {
this.window.disqus_config = function () {
this.page = page;
};

return this.window.disqus_config;
}
}
5 changes: 4 additions & 1 deletion src/app/blog/post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import {
import { ScullyLibModule } from '@scullyio/ng-lib';

import { HighlightService } from '../highlight.service';
import { PostCommentsComponentModule } from './comments.component';

@Component({
selector: 'app-post',
template: `
<!-- This is where Scully will inject the static HTML -->
<scully-content></scully-content>
<app-post-comments></app-post-comments>
`,
styles: [''],
preserveWhitespaces: true,
Expand All @@ -28,6 +31,6 @@ export class PostComponent implements AfterViewChecked {

@NgModule({
declarations: [PostComponent],
imports: [ScullyLibModule],
imports: [ScullyLibModule, PostCommentsComponentModule],
})
export class PostComponentModule {}

0 comments on commit a6e4fba

Please sign in to comment.