From 99f5b872e16f04fda18e8af66e545d7001b6da67 Mon Sep 17 00:00:00 2001 From: David Shevitz Date: Mon, 17 May 2021 23:04:08 +0000 Subject: [PATCH] docs: add new tutorial that explains how to use the UrlMatcher for custom route matching (#42138) PR Close #42138 --- .pullapprove.yml | 4 +- .../e2e/src/app.e2e-spec.ts | 11 ++ .../example-config.json | 0 .../src/app/app.component.css | 3 + .../src/app/app.component.html | 5 + .../src/app/app.component.ts | 10 ++ .../src/app/app.module.ts | 41 +++++ .../src/app/profile/profile.component.css | 0 .../src/app/profile/profile.component.html | 3 + .../src/app/profile/profile.component.ts | 27 ++++ .../routing-with-urlmatcher/src/index.html | 9 ++ .../routing-with-urlmatcher/src/main.ts | 6 + .../routing-with-urlmatcher/stackblitz.json | 9 ++ aio/content/guide/routing-with-urlmatcher.md | 140 ++++++++++++++++++ aio/content/navigation.json | 5 + 15 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 aio/content/examples/routing-with-urlmatcher/e2e/src/app.e2e-spec.ts create mode 100644 aio/content/examples/routing-with-urlmatcher/example-config.json create mode 100644 aio/content/examples/routing-with-urlmatcher/src/app/app.component.css create mode 100644 aio/content/examples/routing-with-urlmatcher/src/app/app.component.html create mode 100644 aio/content/examples/routing-with-urlmatcher/src/app/app.component.ts create mode 100644 aio/content/examples/routing-with-urlmatcher/src/app/app.module.ts create mode 100644 aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.css create mode 100644 aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.html create mode 100644 aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.ts create mode 100644 aio/content/examples/routing-with-urlmatcher/src/index.html create mode 100644 aio/content/examples/routing-with-urlmatcher/src/main.ts create mode 100644 aio/content/examples/routing-with-urlmatcher/stackblitz.json create mode 100644 aio/content/guide/routing-with-urlmatcher.md diff --git a/.pullapprove.yml b/.pullapprove.yml index 7a82ba7934..280a27b6f0 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -584,7 +584,9 @@ groups: 'aio/content/guide/router-reference.md', 'aio/content/examples/router-tutorial/**', 'aio/content/examples/router/**', - 'aio/content/images/guide/router/**' + 'aio/content/images/guide/router/**', + 'aio/content/guide/routing-with-urlmatcher.md', + 'aio/content/examples/routing-with-urlmatcher/**' ]) reviewers: users: diff --git a/aio/content/examples/routing-with-urlmatcher/e2e/src/app.e2e-spec.ts b/aio/content/examples/routing-with-urlmatcher/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..1a20600161 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/e2e/src/app.e2e-spec.ts @@ -0,0 +1,11 @@ +import { browser, element, by } from 'protractor'; + +describe('Routing with Custom Matching', () => { + + beforeAll(() => browser.get('')); + + it('should display Routing with Custom Matching ', async () => { + expect(await element(by.css('h2')).getText()).toEqual('Routing with Custom Matching'); + }); + +}); diff --git a/aio/content/examples/routing-with-urlmatcher/example-config.json b/aio/content/examples/routing-with-urlmatcher/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/routing-with-urlmatcher/src/app/app.component.css b/aio/content/examples/routing-with-urlmatcher/src/app/app.component.css new file mode 100644 index 0000000000..b7ef084c56 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/app/app.component.css @@ -0,0 +1,3 @@ +p { + font-family: Lato; +} \ No newline at end of file diff --git a/aio/content/examples/routing-with-urlmatcher/src/app/app.component.html b/aio/content/examples/routing-with-urlmatcher/src/app/app.component.html new file mode 100644 index 0000000000..82a05fb306 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/app/app.component.html @@ -0,0 +1,5 @@ +

Routing with Custom Matching

+ +Navigate to my profile + + \ No newline at end of file diff --git a/aio/content/examples/routing-with-urlmatcher/src/app/app.component.ts b/aio/content/examples/routing-with-urlmatcher/src/app/app.component.ts new file mode 100644 index 0000000000..f9ed12b95b --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component, VERSION } from '@angular/core'; + +@Component({ + selector: 'my-app', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +}) +export class AppComponent { + name = 'Angular ' + VERSION.major; +} diff --git a/aio/content/examples/routing-with-urlmatcher/src/app/app.module.ts b/aio/content/examples/routing-with-urlmatcher/src/app/app.module.ts new file mode 100644 index 0000000000..2c3d031471 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/app/app.module.ts @@ -0,0 +1,41 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +// #docregion import +import { RouterModule, UrlSegment } from '@angular/router'; +// #enddocregion import + +import { AppComponent } from './app.component'; +import { ProfileComponent } from './profile/profile.component'; + +// #docregion imports-array +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + RouterModule.forRoot([ + { +// #enddocregion imports-array +// #docregion matcher + matcher: (url) => { + if (url.length === 1 && url[0].path.match(/^@[\w]+$/gm)) { + return { + consumed: url, + posParams: { + username: new UrlSegment(url[0].path.substr(1), {}) + } + }; + } + + return null; + }, + component: ProfileComponent + } +// #enddocregion matcher +// #docregion imports-array + ])], + declarations: [ AppComponent, ProfileComponent ], + bootstrap: [ AppComponent ] +}) +// #enddocregion imports-array +export class AppModule { } diff --git a/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.css b/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.html b/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.html new file mode 100644 index 0000000000..c53d80e1a5 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.html @@ -0,0 +1,3 @@ +

+Hello {{ username$ | async }}! +

\ No newline at end of file diff --git a/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.ts b/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.ts new file mode 100644 index 0000000000..4a761f1302 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/app/profile/profile.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +// #docregion activated-route-and-parammap +import { ActivatedRoute, ParamMap } from '@angular/router'; +// #enddocregion activated-route-and-parammap +// #docregion rxjs-map +import { map } from 'rxjs/operators'; +// #enddocregion rxjs-map + +@Component({ + selector: 'app-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.css'] +}) +export class ProfileComponent implements OnInit { +// #docregion subscribe + username$ = this.route.paramMap + .pipe( + map((params: ParamMap) => params.get('username')) + ); +// #enddocregion subscribe +// #docregion activatedroute + constructor(private route: ActivatedRoute) { } +// #enddocregion activatedroute + ngOnInit() { + } + +} diff --git a/aio/content/examples/routing-with-urlmatcher/src/index.html b/aio/content/examples/routing-with-urlmatcher/src/index.html new file mode 100644 index 0000000000..47f5b41707 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/index.html @@ -0,0 +1,9 @@ + + + + Angular App + + + loading + + \ No newline at end of file diff --git a/aio/content/examples/routing-with-urlmatcher/src/main.ts b/aio/content/examples/routing-with-urlmatcher/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/routing-with-urlmatcher/stackblitz.json b/aio/content/examples/routing-with-urlmatcher/stackblitz.json new file mode 100644 index 0000000000..1c644da6b2 --- /dev/null +++ b/aio/content/examples/routing-with-urlmatcher/stackblitz.json @@ -0,0 +1,9 @@ +{ + "description": "Router", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0-9].*" + ], + "tags": ["router-with-custom-matching"] +} diff --git a/aio/content/guide/routing-with-urlmatcher.md b/aio/content/guide/routing-with-urlmatcher.md new file mode 100644 index 0000000000..6bf5c5c682 --- /dev/null +++ b/aio/content/guide/routing-with-urlmatcher.md @@ -0,0 +1,140 @@ +# Tutorial: Creating custom route matches + +The Angular Router supports a powerful matching strategy that you can use to help users navigate your application. This matching strategy supports static routes, variable routes with parameters, wildcard routes, and so on. You can also build your own custom pattern matching for situations in which the URLs are more complicated. + +In this tutorial, you'll build a custom route matcher using Angular's `UrlMatcher`. This matcher looks for a Twitter handle in the URL. + +For a working example of the final version of this tutorial, see the . + +## Objectives + +Implement Angular's `UrlMatcher` to create a custom route matcher. + +## Prerequisites + +To complete this tutorial, you should have a basic understanding of the following concepts: + +* JavaScript +* HTML +* CSS +* [Angular CLI](/cli) + +If you are unfamiliar with how Angular's router works, we recommend you review [Using Angular routes in a single-page application](guide/router-tutorial). + +## Create a sample application + +Using the Angular CLI, create a new application, _angular-custom-route-match_. In addition to the default Angular application framework, you will also create a _profile_ component. + +1. Create a new Angular project, _angular-custom-route-match_. + + + ng new angular-custom-route-match + + + When prompted with `Would you like to add Angular routing?`, select `Y`. + + When prompted with `Which stylesheet format would you like to use?`, select `CSS`. + + After a few moments, a new project, `angular-custom-route-match`, is ready. + +1. From your terminal, navigate to the `angular-custom-route-match` directory. + +1. Create a component, _profile_. + + + ng generate component profile + + +1. In your code editor, locate the file, `profile.component.html` and replace + the placeholder content with the following HTML. + + + +1. In your code editor, locate the file, `app.component.html` and replace + the placeholder content with the following HTML. + + + +## Configure your routes for your application + +With your application framework in place, you next need to add routing capabilities to the `app.module.ts` file. As a part of this process, you will create a custom URL matcher that looks for a Twitter handle in the URL. This handle is identified by a preceding `@` symbol. + +1. In your code editor, open your `app.module.ts` file. + +1. Add an `import` statement for Angular's `RouterModule` and `UrlMatcher`. + + + +1. In the imports array, add a `RouterModule.forRoot([])` statement. + + + +1. Define the custom route matcher by adding the following code to the `RouterModule.forRoot()` statement. + + + +This custom matcher is a function that performs the following tasks: + +* The matcher verifies that the array contains only one segment. +* The matcher employs a regular expression to ensure that the format of the username is a match. +* If there is a match, the function returns the entire URL, defining a `username` route parameter as a substring of the path. +* If there isn't a match, the function returns null and the router continues to look for other routes that match the URL. + +
+ +A custom URL matcher behaves like any other route definition. You can define child routes or lazy loaded routes as you would with any other route. + +
+ +## Subscribe to the route parameters + +With the custom matcher in place, you now need to subscribe to the route parameters in the `profile` component. + +1. In your code editor, open your `profile.component.ts` file. + +1. Add an `import` statement for Angular's `ActivatedRoute` and `ParamMap`. + + + +1. Add an `import` statement for RxJS `map`. + + + +1. Subscribe to the `username` route parameter. + + + +1. Inject the `ActivatedRoute` into the component's constructor. + + + +## Test your custom URL matcher + +With your code in place, you can now test your custom URL matcher. + +1. From a terminal window, run the `ng serve` command. + + + ng serve + + +1. Open a browser to `http://localhost:4200`. + + You should see a single web page, consisting of a sentence that reads `Navigate to my profile`. + +1. Click the **my profile** hyperlink. + + A new sentence, reading `Hello, Angular!` appears on the page. + +## Next steps + +Pattern matching with the Angular Router provides you with a lot of flexibility when you have dynamic URLs in your application. To learn more about the Angular Router, see the following topics: + +* [In-app Routing and Navigation](/guide/router) +* [Router API](/api/router) + +
+ +This content is based on [Custom Route Matching with the Angular Router](https://medium.com/@brandontroberts/custom-route-matching-with-the-angular-router-fbdd48665483), by [Brandon Roberts](https://twitter.com/brandontroberts). + +
diff --git a/aio/content/navigation.json b/aio/content/navigation.json index f9bb689eda..0e92cf50cf 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -276,6 +276,11 @@ "title": "Tutorial: Routing in Single-page Applications", "tooltip": "A tutorial that covers many patterns associated with Angular routing." }, + { + "url": "guide/routing-with-urlmatcher", + "title": "Tutorial: Creating custom route matches", + "tooltip": "Learn how to create a custom URL matcher with the Angular router." + }, { "url": "guide/router-tutorial-toh", "title": "Tutorial: Adding routing to Tour of Heroes",