Angular2 way of converting plain text to url (anchor links)
Asked Answered
B

6

24

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?

Boldface answered 1/9, 2016 at 16:30 Comment(1)
What's the problem with this?Ardith
F
23

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.

Fidellas answered 28/2, 2017 at 11:52 Comment(4)
thank you for this great library but in typescript cannot include 'linkifyjs/string' and i cannot found any typescript support for libraryWedekind
i think we need to bind result to innerHtml if we are using Angular2 ```` <div [innerHTML]="message | linkify "></div> ```Wedekind
You can import the lib like this: var linkifyString = require('linkifyjs/string'); and use in the same way, without any typescript support.Muoimuon
It didn't work for me at first, I kept on receiving string_1.default is not a function error. Eventually I've found that changing the import statement to import * as linkifyString from 'linkifyjs/string'; resolved the issue!Effervesce
I
13

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}}
Imhoff answered 1/9, 2016 at 22:5 Comment(4)
It's returns the "<a href='www.google.com'>" as a string not working like dom element.Lathi
#3104462 there is a JavaScript DOMParser that will convert HTML to JavaScript. I ran into this exact issue recently and you cannot cheat a dom element using stringsImhoff
i think we need to bind result to innerHtml if we are using Angular2 ```` <div [innerHTML]="message | linkify "></div> ```Wedekind
At the start of the linkify() function, add: plainText = plainText.replace("<", "&lt;").replace(">","&gt;"); to stop injection attempts and complaints in the console because of that. Then to use bind to innerHTML as Amr mentionedIllimitable
B
9

You should check angular-linky https://github.com/dzonatan/angular-linky

<span [innerHTML]="yourText | linky"></span>
Beforetime answered 4/11, 2017 at 0:46 Comment(2)
Best for Angular.Selfsustaining
The key here for me was the innerHTML. I already can form my URL....Substantiate
H
8

you should use this pipe in this way:

<div [innerHtml]="note.title | linkify"></div>

Hetaerism answered 9/1, 2017 at 19:37 Comment(0)
N
3

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;
    }  
}
Nesline answered 9/1, 2018 at 14:16 Comment(0)
B
1

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

Boldface answered 1/9, 2016 at 16:53 Comment(3)
There are ways to dynamically inject observable DOM elements post render but its unsafe and heavy. Your best bet is to use a pipe. I know they can be scary, but you use the pipe on the interpolated value, it will manipulate it and spit it back out as u wish. Let me know if this is something youre interested in, ill throw down an example in the answer box and we can work this out.Imhoff
Functions and pipes are essentially the same. The pipe will be modular and cleaner howeverImhoff
hey @MarkAcosta if you want to include it as an answer your welcome, I can accept your answer once its workingBoldface

© 2022 - 2024 — McMap. All rights reserved.