320 | ----
321 |
322 | With this, we complete the migration of our `list` component from AngularJS to Angular! Be sure to rebuild and run the application on `localhost` to verify that everything works as expected.
323 |
324 | NOTE: You may also remove the downgrades of the `card` and `spinner` components which are now only used from an Angular context in our application.
325 |
--------------------------------------------------------------------------------
/11.step-9-migrate-routing-to-angular/3.migrate-routing/index.adoc:
--------------------------------------------------------------------------------
1 | ---
2 | lecture_video: OlXF8gRsK0M
3 | ---
4 | ifndef::ebook[]
5 | include::{docdir}/content/courses/angularjs-migration/_includes/source-code.adoc[]
6 | endif::ebook[]
7 |
8 | = Migrate Routing
9 | :toc:
10 | :toclevels: 5
11 |
12 | We have now removed dual booting and converted our application to bootstrap in Angular. A side-effect of this is the AngularJS UI-Router is now redundant. In this lecture, we will see how to replace the AngularJS `UI-Router` with Angular Router.
13 |
14 | == Adding Angular Router
15 | Before we start, add the `
` tag to the beginning of the `head` element in your `index.html` file. This is mandatory to handle routing in Angular.
16 |
17 | Consider the `app-root.component.ts` file. This contains AngularJS UI-Router directives such as `ui-view`, `ui-sref-active` used for routing. Lets start by converting some of these into Angular Router.
18 |
19 | ==== ui-sref-active directive
20 | `ui-sref-active` is a directive working alongside `ui-sref` to add classes to an element based on the state of the `ui-sref` directive. This can be replaced with the Angular equivalent `RouterLinkActive` directive like so:
21 |
22 | [source, html]
23 | ----
24 | ...
25 |
26 | ...
27 |
28 |
29 | ...
30 |
31 | ...
32 | ----
33 |
34 | ==== ui-view directive
35 | The `ui-view` directive specifies where different templates should be injected into the application using the configurations specified in the `app.routes.ts` file.
36 |
37 | For example, in the `app-root.component.ts` file, the following elements:
38 | ----
39 |
40 | ...
41 |
42 | ----
43 |
44 | will have following template code injected into it as specified in the `app.routes.ts` configuration:
45 |
46 | [source, javascript]
47 | ----
48 | ...
49 | $stateProvider
50 | .state("list", {
51 | url: "/",
52 | views: {
53 | main: {
54 | template: "
",
55 | },
56 | search: {
57 | template: "
",
58 | }
59 | }
60 | }
61 | ...
62 | ----
63 |
64 | The equivalent in Angular is called `RouterOutlet` which acts as a placeholder that Angular dynamically fills based on the current router state.
65 |
66 | Replace the above `div` elements containing the `ui-view` directive with `RouterOutlet` like so:
67 |
68 | [source, html]
69 | ----
70 | ...
71 |
72 | ...
73 |
74 | ...
75 | ----
76 |
77 | Notice we have one unnamed router outlet for our `main` template and another (renamed as `header`) for our `search` template.
78 |
79 | ==== ui-sref directive
80 | The `ui-sref` directive binds a link to a state. If the state has an associated URL, the directive will automatically generate and update the `href` attribute using the `$state.href()` method. The equivalent in Angular is `RouterLink` which lets you link specific routes in your application.
81 |
82 | In our template code in `app-root.component.ts`, we have the following `ui-sref` references:
83 | [source, html]
84 | ----
85 | ...
86 |
Search
87 | ...
88 |
Create
89 | ...
90 | ----
91 | we can replace the `Search` link (which relates to the base URL of the application) with the following `RouterLink` equivalent:
92 | [source, html]
93 | ----
94 |
Search
95 | ----
96 |
97 | Similarly, we can replace the `ui-sref="create"` directive such that, when we navigate to the `Create` page in our application, we do not display the search functionality in the header.
98 |
99 | We can accomplish this like so:
100 |
101 | [source, html]
102 | ----
103 |
Create
104 | ----
105 |
106 | The above code loads the `person-create` component in our primary outlet, while nothing is loaded into the `header` router outlet, which is the required behavior for our application. The concept behind this is called Named Router-Outlets which lets us target multiple router outlets using a single `routerLink`.
107 |
108 | == Modifying application routes
109 | Next, we will replace the older AngularJS route configurations with the following Angular routing code in `app.routes.ts` like so:
110 |
111 | [source, javascript]
112 | ----
113 | import {Routes} from "@angular/router";
114 |
115 | import {SearchComponent} from "./components/search.component";
116 | import {PersonListComponent} from "./components/person-list.component";
117 | import {PersonCreateComponent} from "./components/person-create.component";
118 | import {PersonEditComponent} from "./components/person-edit.component";
119 |
120 | export const routes: Routes = [
121 | {path: '', redirectTo: '/list(header:search)', pathMatch: 'full'},
122 | {path: 'list', component: PersonListComponent},
123 | {path: 'search', component: SearchComponent, outlet: 'header'},
124 | {path: 'create', component: PersonCreateComponent},
125 | {path: 'edit/:email', component: PersonEditComponent},
126 | ];
127 | ----
128 |
129 | The above configuration basically specifies which components to load for the corresponding URL path. If an outlet is not specified for a given `path`, then it is loaded onto the `main` outlet.
130 |
131 | Notice the root path with the `redirectTo` property. This special syntax redirects to the `list` path while injecting the `search` path into the `header` component.
132 |
133 | NOTE: We will not be discussing the routing configuration in detail as this is pretty standard Angular routing code. if you do need a refresher, check out my free https://codecraft.tv/courses/angular/routing/overview/[Angular course] which covers all of this in great detail.
134 |
135 | Next, to use the Angular Router in our application, we have to _provide_ the configuration (`app.routes.ts`) and the Angular `RouterModule` in our `NgModule` like so:
136 |
137 | [source, javascript]
138 | ----
139 | ...
140 | import { RouterModule } from "@angular/router";
141 | ...
142 | import {routes} from './app.routes'
143 |
144 | @NgModule({
145 | imports: [
146 | BrowserModule,
147 | UpgradeModule,
148 | HttpClientModule,
149 | FormsModule,
150 | ReactiveFormsModule,
151 | LaddaModule,
152 | InfiniteScrollModule,
153 | ToasterModule,
154 | RouterModule.forRoot(routes, {useHash: true})
155 | ],
156 | providers: [
157 | ...
158 | ],
159 | declarations: [
160 | ...
161 | ],
162 | bootstrap: [
163 | ...
164 | ]
165 | })
166 | ...
167 | ----
168 |
169 | == Modifying application code to use Angular Router
170 | Our application code still contains AngularJS UI-Router code. Lets change that.
171 |
172 | Consider the `person-edit.component.ts` file:
173 | [source, javascript]
174 | ----
175 | ...
176 | export class PersonEditComponent {
177 | public mode: string = 'Edit';
178 | public person: any;
179 |
180 | //<1>
181 | constructor(@Inject(UIRouterStateParams) private $stateParams,
182 | @Inject(UIRouterState) private $state,
183 | @Inject(ContactService) public contacts: ContactService) {
184 | //<2>
185 | this.person = this.contacts.getPerson(this.$stateParams.email);
186 | }
187 |
188 | save() {
189 | this.contacts.updateContact(this.person).then(() => {
190 | //<3>
191 | this.$state.go("list");
192 | });
193 | };
194 |
195 | remove() {
196 | this.contacts.removeContact(this.person).then(() => {
197 | this.$state.go("list");
198 | });
199 | };
200 |
201 | }
202 | ...
203 | ----
204 |
205 | * Both the `UIRouterStateParams` and the `UIRouterState` can be replaced by the Angular equivalent `ActivatedRoute` and the `Router` services respectively.
206 |
207 | TIP: The `ActivatedRoute` contains information about a route associated with a component loaded in an outlet, while the `Router` manages navigation between different components in the application.
208 |
209 | * The equivalent code for `this.$state.go` in Angular is `this.router.navigate([''])`, where the `navigate` function takes as arguments the path to the component to be navigated to.
210 |
211 | * The `$stateParams` usage can be replaced with the following code:
212 |
213 | [source, javascript]
214 | ----
215 | this.route.params.subscribe(params => {
216 | console.log(params);
217 | if (params['email']) {
218 | this.person = this.contacts.getPerson(params['email']);
219 | }
220 | });
221 | ----
222 |
223 | This code essentially gives the same functionality we had with our AngularJS implementation.
224 |
225 | The `PersonEditComponent` modified to use Angular Router will be like so:
226 | [source, javascript]
227 | ----
228 | ...
229 | import {Router, ActivatedRoute} from "@angular/router";
230 | ...
231 | export class PersonEditComponent {
232 | public mode: string = 'Edit';
233 | public person: any;
234 |
235 | constructor(private route: ActivatedRoute,
236 | private router: Router,
237 | @Inject(ContactService) public contacts: ContactService) {
238 | this.route.params.subscribe(params => {
239 | console.log(params);
240 | if (params['email']) {
241 | this.person = this.contacts.getPerson(params['email']);
242 | }
243 | });
244 |
245 | }
246 |
247 | save() {
248 | this.contacts.updateContact(this.person).then(() => {
249 | this.router.navigate(['']);
250 | });
251 | };
252 |
253 | remove() {
254 | this.contacts.removeContact(this.person).then(() => {
255 | this.router.navigate(['']);
256 | });
257 | };
258 |
259 | }
260 | ...
261 | ----
262 |
263 | Similarly, we can modify the `person-create.component.ts` file to use the Angular Router for its routing requirements.
264 |
265 | Next consider the `card.component.ts` file. Its template code uses an `href` attribute to allow navigation for a person edit like so:
266 | [source, html]
267 | ----
268 | ...
269 | [attr.href]="'#!/edit/' + user.email">
270 | ...
271 | ----
272 |
273 | This is again AngularJS UI-Router functionality which we can easily replace with our `RouterLink` attribute like so:
274 | [source, html]
275 | ----
276 | ...
277 | [routerLink]="['/edit', user.email]">
278 | ...
279 | ----
280 |
281 |
282 | == Cleaning up the code
283 | With the migration to Angular Router, most of our previous code related to AngularJS UI-Router is now redundant. Therefore, lets quickly clean it up like so:
284 |
285 | * Remove the upgrade code for the `UIRouterStateProvider` and `UIRouterStateParams` from the `ajs-upgraded-provider.ts` file.
286 |
287 | * Remove the following component downgrade logic (and the relevant imports!) from our component files:
288 |
289 | [source, javascript]
290 | ----
291 | angular
292 | .module('codecraft')
293 | .directive('personEdit', downgradeComponent({
294 | component: PersonEditComponent
295 | }));
296 | ----
297 |
298 | Re-build the application and run it on `localhost`. Your application will still not work, and if you access the browser console you may notice an error related to the Toaster module. But this is expected. We will look at how to fix this in the next lecture!
299 |
--------------------------------------------------------------------------------