Skip to content

Commit

Permalink
Add support for standalone components (#484)
Browse files Browse the repository at this point in the history
* feat: add `provideMarkdown` as a new way to configure ngx-markdown

* chore: run ng generate @angular/core:standalone

* chore: run ng generate @angular/core:standalone

* chore: run ng generate @angular/core:standalone

* chore: move routing to the new standalone

* chore: move to the new provideMarkdown

* chore: cleanup the code and fix errors

* chore: address review comments

* Remove MarkdownModule deprecation

* Remove unnecessary added line between get/set

* Fix demo anchor scrolling

* Add provideMarkdown documentation in README.md

* Add provideMarkdown documentation in demo

---------

Co-authored-by: jfcere <[email protected]>
  • Loading branch information
robertIsaac and jfcere authored Nov 13, 2023
1 parent 49a22eb commit e1245d4
Show file tree
Hide file tree
Showing 57 changed files with 465 additions and 520 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"order": "asc",
"caseInsensitive": true
},
"newlines-between": "never",
"pathGroups": [
{
"pattern": "@*/**",
Expand Down
162 changes: 131 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,25 @@ To customize the default button styling, use the `.markdown-clipboard-button` CS

#### Using global configuration

You can provide a custom component to use globaly across your application with the `clipboardOptions` via `MarkdownModule.forRoot()` import configuration.
You can provide a custom component to use globaly across your application with the `clipboardOptions` in the `MarkdownModuleConfig` either with `provideMarkdown` provide-function for standalone components or `MarkdownModule.forRoot()` for module configuration.

##### Using the `provideMarkdown` function

```typescript
provideMarkdown({
clipboardOptions: {
provide: CLIPBOARD_OPTIONS,
useValue: {
buttonComponent: ClipboardButtonComponent,
},
},
})
```

##### Using the `MarkdownModule` import

```typescript
MarkdownModule.forRoot({
...
clipboardOptions: {
provide: CLIPBOARD_OPTIONS,
useValue: {
Expand Down Expand Up @@ -477,14 +491,30 @@ Alternatively, the `clipboard` directive can be used in conjonction with `ng-tem
## Configuration

### Main application module
The ngx-markdown library can be used either with the standalone components or with modules configuration. Please follow the configuration section that matches your application.

### Standalone components

You must import `MarkdownModule` inside your main application module (usually named AppModule) with `forRoot` to be able to use `markdown` component and/or directive.
Use the `provideMarkdown` provide-function in your application configuration `ApplicationConfig` to be able to provide the `MarkdownComponent` and `MarkdownPipe` to your standalone components and/or inject the `MarkdownService`.

```diff
import { NgModule } from '@angular/core';
+ import { MarkdownModule } from 'ngx-markdown';
+ import { provideMarkdown } from 'ngx-markdown';

export const appConfig: ApplicationConfig = {
providers: [
+ provideMarkdown(),
],
};
```

### Modules configuration

You must import `MarkdownModule` inside your main application module (usually named AppModule) with `forRoot` to be able to use the `markdown` component, directive, pipe and/or `MarkdownService`.

```diff
import { NgModule } from '@angular/core';
+ import { MarkdownModule } from 'ngx-markdown';
import { AppComponent } from './app.component';

@NgModule({
Expand All @@ -497,8 +527,37 @@ import { AppComponent } from './app.component';
export class AppModule { }
```

Use `forChild` when importing `MarkdownModule` into other application modules to allow you to use the same parser configuration across your application.

```diff
import { NgModule } from '@angular/core';
+ import { MarkdownModule } from 'ngx-markdown';
import { HomeComponent } from './home.component';

@NgModule({
imports: [
+ MarkdownModule.forChild(),
],
declarations: [HomeComponent],
})
export class HomeModule { }
```

### Remote file configuration

If you want to use the `[src]` attribute to directly load a remote file, in order to keep only one instance of `HttpClient` and avoid issues with interceptors, you also have to provide `HttpClient`:

##### Using the `provideMarkdown` function

```diff
providers: [
+ provideHttpClient(),
+ provideMarkdown({ loader: HttpClient }),
],
```

##### Using the `MarkdownModule` import

```diff
imports: [
+ HttpClientModule,
Expand All @@ -510,6 +569,22 @@ imports: [

As of ngx-markdown v9.0.0 **sanitization is enabled by default** and uses Angular `DomSanitizer` with `SecurityContext.HTML` to avoid XSS vulnerabilities. The `SecurityContext` level can be changed using the `sanitize` property when configuring `MarkdownModule`.

##### Using the `provideMarkdown` function

```typescript
import { SecurityContext } from '@angular/core';

// enable default sanitization
provideMarkdown()

// turn off sanitization
provideMarkdown({
sanitize: SecurityContext.NONE
})
```

##### Using the `MarkdownModule` import

```typescript
import { SecurityContext } from '@angular/core';

Expand Down Expand Up @@ -547,19 +622,37 @@ You can bypass sanitization using the markdown component, directive or pipe usin

Optionally, markdown parsing can be configured using [MarkedOptions](https://marked.js.org/#/USING_ADVANCED.md#options) that can be provided with the `MARKED_OPTIONS` injection token via the `markedOptions` property of the `forRoot` method of `MarkdownModule`.

Imports:
##### Using the `provideMarkdown` function

```typescript
import { MarkdownModule, MARKED_OPTIONS } from 'ngx-markdown';
// imports
import { MARKED_OPTIONS, provideMarkdown } from 'ngx-markdown';

// using default options
provideMarkdown(),

// using specific options with ValueProvider and passing HttpClient
provideMarkdown({
markedOptions: {
provide: MARKED_OPTIONS,
useValue: {
gfm: true,
breaks: false,
pedantic: false,
},
},
}),
```

Default options:
##### Using the `MarkdownModule` import

```typescript
// imports
import { MarkdownModule, MARKED_OPTIONS } from 'ngx-markdown';

// using default options
MarkdownModule.forRoot(),
```

Custom options and passing `HttpClient` to use `[src]` attribute:
```typescript
// using specific options with ValueProvider and passing HttpClient
MarkdownModule.forRoot({
loader: HttpClient, // optional, only if you use [src] attribute
Expand All @@ -578,7 +671,7 @@ MarkdownModule.forRoot({

`MarkedOptions` also exposes the `renderer` property which allows you to override token rendering for your whole application.

The example below overrides the default blockquote token rendering by adding a CSS class for custom styling when using Bootstrap CSS:
The example uses a factory function and override the default blockquote token rendering by adding a CSS class for custom styling when using Bootstrap CSS:

```typescript
import { MARKED_OPTIONS, MarkedOptions, MarkedRenderer } from 'ngx-markdown';
Expand All @@ -598,47 +691,54 @@ export function markedOptionsFactory(): MarkedOptions {
pedantic: false,
};
}
```

##### Using the `provideMarkdown` function

```typescript
// using specific option with FactoryProvider
MarkdownModule.forRoot({
loader: HttpClient,
provideMarkdown({
markedOptions: {
provide: MARKED_OPTIONS,
useFactory: markedOptionsFactory,
},
}),
```

##### Using the `MarkdownModule` import

```typescript
// using specific option with FactoryProvider
MarkdownModule.forRoot({
markedOptions: {
provide: MARKED_OPTIONS,
useFactory: markedOptionsFactory,
},
}),
```

### Marked extensions

You can provide [marked extensions](https://marked.js.org/using_advanced#extensions) using the `markedExtensions` property that accepts an array of extensions when configuring `MarkdownModule`.

```ts
##### Using the `provideMarkdown` function

```typescript
import { gfmHeadingId } from 'marked-gfm-heading-id';

MarkdownModule.forRoot({
providemarkdown({
markedExtensions: [gfmHeadingId()],
}),
```

### Other application modules

Use `forChild` when importing `MarkdownModule` into other application modules to allow you to use the same parser configuration across your application.

```diff
import { NgModule } from '@angular/core';
+ import { MarkdownModule } from 'ngx-markdown';
##### Using the `MarkdownModule` import

import { HomeComponent } from './home.component';
```typescript
import { gfmHeadingId } from 'marked-gfm-heading-id';

@NgModule({
imports: [
+ MarkdownModule.forChild(),
],
declarations: [HomeComponent],
})
export class HomeModule { }
MarkdownModule.forRoot({
markedExtensions: [gfmHeadingId()],
}),
```

## Usage
Expand Down
38 changes: 38 additions & 0 deletions demo/src/app/app-routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Routes } from '@angular/router';

export const appRoutes: Routes = [
{
path: 'get-started',
loadComponent: () => import('./get-started/get-started.component'),
data: { label: 'Get Started' },
},
{
path: 'cheat-sheet',
loadComponent: () => import('./cheat-sheet/cheat-sheet.component'),
data: { label: 'Cheat Sheet' },
},
{
path: 'syntax-highlight',
loadComponent: () => import('./syntax-highlight/syntax-highlight.component'),
data: { label: 'Syntax Highlight' },
},
{
path: 'bindings',
loadComponent: () => import('./bindings/bindings.component'),
data: { label: 'Bindings' },
},
{
path: 'plugins',
loadComponent: () => import('./plugins/plugins.component'),
data: { label: 'Plugins' },
},
{
path: 're-render',
loadComponent: () => import('./rerender/rerender.component'),
data: { label: 'Re-render' },
},
{
path: '**',
redirectTo: 'get-started',
},
];
51 changes: 0 additions & 51 deletions demo/src/app/app-routing.module.ts

This file was deleted.

26 changes: 22 additions & 4 deletions demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { DOCUMENT } from '@angular/common';
import { DOCUMENT, NgFor } from '@angular/common';
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Inject, OnInit, ViewChild } from '@angular/core';
import { Route, Router, RouterOutlet } from '@angular/router';

import { AnchorService } from '@shared/anchor/anchor.service';
import { FlexModule } from '@angular/flex-layout/flex';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { Route, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AnchorService } from '@shared/anchor';
import { ROUTE_ANIMATION } from './app.animation';
import { DEFAULT_THEME, LOCAL_STORAGE_THEME_KEY } from './app.constant';
import { isTheme, Theme } from './app.models';
Expand All @@ -13,6 +17,18 @@ import { isTheme, Theme } from './app.models';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
FlexModule,
MatButtonModule,
MatIconModule,
MatTabsModule,
MatToolbarModule,
NgFor,
RouterLink,
RouterLinkActive,
RouterOutlet,
],
})
export class AppComponent implements OnInit {

Expand Down Expand Up @@ -55,6 +71,8 @@ export class AppComponent implements OnInit {
}

ngOnInit(): void {
this.anchorService.setOffset([0, 64]);

const storedTheme = localStorage.getItem(LOCAL_STORAGE_THEME_KEY);
this.setTheme(
isTheme(storedTheme)
Expand Down
Loading

0 comments on commit e1245d4

Please sign in to comment.