11

I am using an Angular 2 Router to update the query params in a URL for a search application. I am attempting to replace spaces in a query with + signs. However, + signs are getting encoded. For example:

this.router.navigatebyUrl('?q=one+two');

populates the URL with "?q=one%2Btwo".

In looking at the source code for Angular 2, it looks like the router converts the URL to a UrlTree which uses encodeURIComponent() to encode the url. Because of this, it is impossible to prevent the default encoding.

My current process is that I change the route by doing navigateByUrl as seen above, and then listen for changes with:

this.routeSubscription = this.route.queryParams.subscribe((params: any) => {
  this.term = (params.q ? params.q : '');
});

Is there an alternate way to deal with query parameters that would allow me to use my own strategy for url encoding?

Noah Mulfinger
  • 351
  • 1
  • 2
  • 8
  • Possible duplicate of [angular 2 disable url encoding](http://stackoverflow.com/questions/41476193/angular-2-disable-url-encoding) – jigar gala Feb 09 '17 at 16:34

3 Answers3

24

I was able to find a solution to my problem. You can make own custom url serializer by implementing the UrlSerializer class.

Custom Url Serializer

Create a custom url serializer like this:

@Injectable()
export class CustomUrlSerializer implements UrlSerializer {

    constructor(private defaultUrlSerializer: DefaultUrlSerializer){}

    parse(url: string): UrlTree {
        // Custom code here
    }

    serialize(tree: UrlTree): string {
        // Custom code here
    }
}

Then, you just need to provide the CustomUrlSerializer in place of the UrlSerializer, which you can place in the AppModule providers array after importing both serializers.

providers: [
    { provide: UrlSerializer, useClass: CustomUrlSerializer },
       DefaultUrlSerializer
    ...
]

Now, when you call router.navigate or router.navigateByUrl, it will use your custom serializer for parsing and serializing.

Using + signs as spaces

To parse + signs as spaces:

parse(url: string): UrlTree {
    // Change plus signs to encoded spaces
    url = url.replace(/\+/g, '%20');
    // Use the default serializer that you can import to just do the 
    // default parsing now that you have fixed the url.
    return this.defaultUrlSerializer.parse(url)  
}

And for serializing:

serialize(tree: UrlTree): string {
    // Use the default serializer to create a url and replace any spaces with + signs
    return this.defaultUrlSerializer.serialize(tree).replace(/%20/g, '+');
}

DefaultUrlSerializer

Maurice
  • 6,698
  • 9
  • 47
  • 104
Noah Mulfinger
  • 351
  • 1
  • 2
  • 8
  • 4
    Anyone encountered the error `ERROR in Invalid provider for undefined. useClass cannot be null.`? – koppor Aug 02 '17 at 18:47
  • I ran into that issue as well. I added an answer to this question that explains my workaround. – Aaron Scherbing Aug 14 '18 at 19:44
  • I am getting 404 error on page refresh when using + as spaces, after uploaded my angular project on IIS, it is working fine in localhost. Can anyone provide suggestion to solve this? – Darshana Aug 15 '20 at 10:49
  • I tried this solution in Angular 9.1 and it had complained, that it does not know `this.defaultSerializer`. Replacing `implements UrlSerializer` by `extends DefaultUrlSerializer` and `this.defaultUrlSerializer.parse` by `this.parse` as well as `this.defaultSerializer.serialize` by `this.serialize` has solved the problem. -- The reason of the problem seems to be that UrlSerializer is an interface and as such contains no implementation of that method. – Olli Aug 16 '20 at 07:06
6

I ran into an issue specifying the custom provider. There was apparently a circular dependency when compiling with the --prod flag causing an error with the text: useClass cannot by null. This is how I worked around that error:

In AppModule, define the following:

const customUrlSerializer = new CustomUrlSerializer();
const CustomUrlSerializerProvider = {
    provide: UrlSerializer,
    useValue: customUrlSerializer
};

Then in the providers array, add the provider you specified above.

...
providers: [CustomUrlSerializerProvider]
...
Aaron Scherbing
  • 507
  • 6
  • 6
0

I tried @Noahs solution in Angular 9.1 for avoiding that a ? is replaced by %3F:

However, this did not work:

class CustomUrlSerializer implements UrlSerializer {
  parse(url: string): UrlTree {
    return this.defaultSerializer.parse(url)  
  }

  serialize(tree: UrlTree): string {
    return this.defaultSerializer.serialize(tree).replace(/%3F/g, '?');
  }
}

Angular had complained with following error message

client:159 src/app/app.module.ts:36:17 - error TS2339: Property 'defaultSerializer' does not exist on type 'CustomUrlSerializer'.

The following code has worked for me, though:

class CustomUrlSerializer extends DefaultUrlSerializer {
  parse(url: string): UrlTree {
    return super.parse(url)  
  }

  serialize(tree: UrlTree): string {
    return super.serialize(tree).replace(/%20/g, '+');
  }
}

There, I have replaced:

implements UrlSerializer --> extends DefaultUrlSerializer
this.defaultSerializer   --> super

Note: I have removed the following back-translation, since it replaces ? by %3F before routing an URL, but we want to keep it in this case:

  parse(url: string): UrlTree {
    url = url.replace(/\?/g, '%3F'); // <--- removed
    ...

Olli
  • 1,621
  • 15
  • 18