I sometimes have a component that can receive text like this:
text www.website.com
But I would like to convert it to a url if it is a link. Like this.
text www.website.com
I read this SO answer that suggests using 3rd party libs such as anchorme. Is there anywway to do it the angular2 way?
There are numerous problems with using simple regexes to modify HTML content.
Here's an approach that uses the linkifyjs module, which you need to npm install
. Do notice that the input is considered plain text, while output is HTML text.
import { Pipe, PipeTransform } from '@angular/core';
import linkifyStr from 'linkifyjs/string';
@Pipe({name: 'linkify'})
export class LinkifyPipe implements PipeTransform {
transform(str: string): string {
return str ? linkifyStr(str, {target: '_system'}) : str;
}
}
NB: If you need to specify the target
attributes, add eg. {target: '_system'}
as a second parameter to linkifyStr
.
Okay so to make a pipe you would make a pipe component consisting of
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'linkify'})
export class LinkifyPipe implements PipeTransform {
transform(link: string): string {
return this.linkify(link);
}
private linkify(plainText): string{
let replacedText;
let replacePattern1;
let replacePattern2;
let replacePattern3;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = plainText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
return replacedText;
}
}
then import this like u would a directive, pass it to the
pipes: [LinkifyPipe]
and interpolate like this
{{url | linkify}}
you should use this pipe in this way:
<div [innerHtml]="note.title | linkify"></div>
You should check angular-linky https://github.com/dzonatan/angular-linky
<span [innerHTML]="yourText | linky"></span>
I have searched for a solution but to no avail. I needed to extent the requirement to handle Angular routing [routerLink] and external links with [innerHTML] without the need for 3rd party libs.
My solution (summary):
Angular 2, Angular 5 - User click events/event handling for dynamic [innerHTML] generated content using pipe and directive to generate and converting plain text to click urls e.g #hashtags, @Handle, @Mention, #routerLink #href and #mailto etc
Plunker demo:
https://embed.plnkr.co/68lZFY/
AppComponent
import { Component, NgModule, VERSION} from '@angular/core';
import { BrowserModule} from '@angular/platform-browser';
@Component({
selector: 'my-app',
template: `<h1>Angular - Dynamic content click event</h1>
<p>Angular 2, Angular 5, Typescript - User click events/event handling for dynamic [innerHTML] generated content using pipe and directive to generate and converting plain text to click urls e.g #hashtags, @Handle, @Mention, #routerLink #href and #mailto etc</p>
<ul>
<li *ngFor="let item of theList; let $index=index;" [innerHTML]="item | parseUrl" [dynamicContent]="currentView"></li>
<ul>`
})
export class AppComponent {
theList:Array;
constructor() {
this.theList = [
'Lorem ipsum dolor sit amet, consectetur @adet dolore magna aliqua. Ut enim ad minim veniam',
'Lorem ipsum dolor sit amet, consectetur adipiscing http://google.com sed do eiusmod tempor #incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam',
'Lorem http://google.com ipsum dolor #sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt gna aliqua. Ut enim ad minim veniam',
'Lorem ipsum @dolor sit amet, consectetur @adipiscing elit, sed do eiusmod @tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam',
'Lorem ipsum dolor sit amet, smod tempor incididunt #ut labore et dolore @magna #aliqua. Ut enim ad minim veniam',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam',
'Lorem ipsum @dolor sit amet, #consectetur adipiscing elit, sed do eiusmod tempor http://google.com enim ad minim veniam'
];
}
}
Directive
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[dynamicContent]'
})
export class DynamicContent {
constructor(private el: ElementRef) { }
@Input('dynamicContent') dynamicContent: string;
@HostListener('click', ['$event']) onClick(e) {
if (e.target.classList.contains('handle-link')) {
let link: string = e.target.innerHTML;
event.preventDefault();
event.stopPropagation();
alert("/search/handle/" + link.trim());
//this.router.navigateByUrl("/search/handle/" + link.trim(), { skipLocationChange: false });
} else if (e.target.classList.contains('hashtag-link')) {
let link: string = e.target.innerHTML;
event.preventDefault();
event.stopPropagation();
alert("/search/hashtag/" + link.trim());
//this.router.navigateByUrl("/search/hashtag/" + link.trim(), { skipLocationChange: false });
}
}
}
Pipe
export class ParseUrl implements PipeTransform {
urls: any = /(\b(https?|http|ftp|ftps|Https|rtsp|Rtsp):\/\/[A-Z0-9+&@#\/%?=~_|!:,.;-]*[-A-Z0-9+&@#\/%=~_|])/gim; // Find/Replace URL's in text
hashtags: any = /(^|\s)(#[a-z\d][\w-]*)/ig; // Find/Replace #hashtags in text
mentions: any = /(^|\s)(@[a-z\d][\w-]*)/ig; // Find/Replace @Handle/Mentions in text
emails: any = /(\S+@\S+\.\S+)/gim; // Find/Replace email addresses in text
transform(text: string) {
return this.parseUrl(text);
}
private parseUrl(text: string) {
// Find/Replace URL's in text
if (text.match(this.urls)) {
text = text.replace(this.urls, function replacer($1, $2, $3) {
let url: any = $1;
let urlClean: any = url.replace("" + $3 + "://", "");
return "<a href=\"" + url + "\" target=\"_blank\">" + urlClean + "</a>";
});
}
// Find/Replace @Handle/Mentions in text
if (text.match(this.hashtags)) {
text = text.replace(this.hashtags, "<a href=\"/search/hashtag/$2\" class=\"hashtag-link\">$1$2</a>");
}
// Find/Replace #hashtags in text
if (text.match(this.mentions)) {
text = text.replace(this.mentions, "<a href=\"/search/handle/$2\" class=\"handle-link\">$1$2</a>");
}
// Find/Replace email addresses in text
if (text.match(this.emails)) {
text = text.replace(this.emails, "<a href=\"mailto:$1\">$1</a>");
}
return text;
}
}
Ok this is how I did it leaving answer hope it helps someone else:
So I'm using a function to linkify my plaint text
private linkify(plainText): string{
let replacedText;
let replacePattern1;
let replacePattern2;
let replacePattern3;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = plainText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
return replacedText;
}
But this returns a string with html encoding so if I use in with <p>{{example}}</p>
it will return full encoding (inlcuding anchor tags and html).
So now I use angular2 builtin html binding:
This gives me the solution