npm install --save ngx-link-preview
# For Angular <= 13
npm install --save [email protected]
yarn add ngx-link-preview
- Configurable preview
- Render array of links
- Parse links in string
- Loading indicator
- Theming:
default
andmodern
theme included
- Angular 6+
- You will need to create an endpoint at your backend to parse an url for meta tags.
See info below
@NgModule({
imports: [
NgxLinkPreviewModule,
...
]
})
export class AppModule { }
<ngx-link-preview></ngx-link-preview>
apiRoute accepts any url, where you want to retrieve the metadata from. The target url will be attached by the component as base64 urlencoded query parameter.
/** API route where to get the meta data from, component will build the full request url
* Schema: api.example.com/api/get-meta-data?url=d3d3LmV4YW1wbGUuY29t
*/
@Input()
public apiRoute: string;
A generic callback function that returns an observable, that runs the api request on subscription. You can use the default Angular HttpClient method, or your configured backend wrapper observable.
/** Method that does the API request, provide as class member arrow function from parent */
@Input()
public getApiEndpoint$: (requestUrl: string) => Observable<any>;
For example:
public apiCallbackFn = (route: string) => {
return this.http.get(route);
};
/** Plain links string array */
@Input()
public links: string[] = [];
/** Input string to parse for links */
@Input()
public parseForLinksStr: string;
/** Target url will be attached as encodeURI(btoa(url)), so it must be decoded on the server */
@Input()
public queryParamName = 'url';
/** boolean: show image in preview */
@Input()
public showImage = true;
/** boolean: show site name in preview */
@Input()
public showSiteName = true;
/** boolean: show title in preview */
@Input()
public showTitle = true;
/** boolean: show description in preview */
@Input()
public showDescription = true;
/** boolean: show link url in preview */
@Input()
public showLinkUrl = false;
/** boolean: use cache to display previews faster on next rendering */
@Input()
public useCache = true;
/** number: max age the data cache of a link preview should be used */
@Input()
public maxCacheAgeMs = 1000 * 60 * 60 * 24 * 7; // 7 days
/** boolean: show loading indicator */
@Input()
public showLoadingIndicator = true;
/**
* boolean: whether the <a href="..."></a> link should be clickable.
* This is a question of context security. Otherwise use (previewClick) event.
*/
@Input()
public useHtmlLinkDefaultClickEvent = false;
/**
* HtmlLinkTarget: where the HTML link should be opened on click.
* Only has an effect if [useHtmlLinkDefaultClickEvent]="true"
*/
@Input()
public htmlLinkTarget: HtmlLinkTarget = '_blank';
/** Event emitter: on click to handle the click event, emits the clicked URL */
@Output()
public previewClicked = new EventEmitter<string>();
<!-- $event: string is URL -->
(previewClick)="previewClicked($event)"
The package ships a default and a modern theme. The modern theme crops the preview image, what might conclude in unintended results. To use the modern theme, just pass the css class:
<ngx-link-preview class="modern"></ngx-link-preview>
Feel free to create more themes and submit a pull request or open an issue!
You can use this skeleton: (click to show)
.ngx-link-preview-container {
.og-link-preview {
&:hover {
}
.row {
.col {
&.preview-image {
}
&.text-data {
}
.image {
img {
}
}
.title {
}
.description {
}
.header {
.site-name {
}
}
.footer {
.url {
}
}
}
}
}
}
You can customize the loading spinner by passing your spinner as content of the component:
<ngx-link-preview>
<my-spinner-component></my-spinner-component>
</ngx-link-preview>
<!-- or alternatively (any child element): -->
<ngx-link-preview>
<div class="spinner"></div>
</ngx-link-preview>
With node.js you can use url-metadata
import urlMetadata from 'url-metadata';
public getMetadata(request: Request, response: Response) {
let { url } = request.query;
url = this.decodeSafeUrl(url);
urlMetadata(url).then(
resp => {
response.send(resp);
},
error => {
response.send(error).status(500);
}
);
}
public decodeSafeUrl(value: string): string {
const valueBase64 = decodeURI(value);
return Buffer.from(valueBase64, 'base64').toString('utf8');
}
$router->get('/meta-tags', function() {
$url = $_GET['url'];
$urlDecoded = base64_decode(urldecode($url));
ini_set('user_agent', 'Mozilla/4.0 (compatible; MSIE 6.0)');
$sites_html = file_get_contents($urlDecoded);
$html = new DOMDocument();
@$html->loadHTML($sites_html);
$metaTags = [
'title' => '',
'description' => '',
'image' => '',
'canonical' => '',
'url' => '',
'author' => '',
'availability' => '',
'keywords' => '',
'og:description' => '',
'og:determiner' => '',
'og:image' => '',
'og:image:height' => '',
'og:image:secure_url' => '',
'og:image:type' => '',
'og:image:width' => '',
'og:locale' => '',
'og:locale:alternate' => '',
'og:site_name' => '',
'og:title' => '',
'og:type' => '',
'og:url' => '',
'price' => '',
'priceCurrency' => '',
'source' => '',
];
foreach ($html->getElementsByTagName('meta') as $meta) {
$property = $meta->getAttribute('property');
$content = $meta->getAttribute('content');
if (strpos($property, 'og') === 0) {
$metaTags[$property] = $content;
if ($property === 'og:title') $metaTags['title'] = $property;
if ($property === 'og:description') $metaTags['description'] = $property;
if ($property === 'og:image') $metaTags['image'] = $property;
}
}
$metaTags['canonical'] = $urlDecoded;
$metaTags['url'] = $urlDecoded;
return response()->json($metaTags);
});