神刀安全网

Component-Relative paths in Angular 2

Easily creating components is the most-loved feature of Angular 2. By now you should be familiar with using the @Component decorators to create components. You are probably already familiar with the required metadata information such as selector and template ; if not you might want to read our article on building a zipping component .

If you happen to be lucky, your components and their HTML/CSS load without any problems. Perhaps you have already (or soon will) encounter the dreaded, frustrating 404 component-loading errors where the template HTML or styles (CSS) cannot be found!

Let’s talk about why that happens and see how we can solve that problem in a way that is flexible and portable.

Before we jump, however, into the actual problem we want to solve, let’s first review two (2) types of component implementations.

Components with Inline Metadata

For every Angular 2 component that we implement, we define not only an HTML template, but may also define the CSS styles that go with that template, specifying any selectors, rules, and media queries that we need.

One way to do this is to set the styles and template property in the component metadata.

Consider a simple Header component… which we actually use in our Angular 2 Master Class training.

header.component.ts

import { Component, OnInit } from '@angular/core';  @Component({   selector: 'contacts-header',   template: `     <nav class="navbar-fixed">       <div class="nav-wrapper">         <span class="brand-logo center">Contacts</span>       </div>     </nav>   `,   styles: ['.navbar-fixed { position:fixed; }'] }) export class HeaderComponent implements OnInit { }

Using this component is super easy and since there are no external file dependencies and everything is defined inline. You should never see a 404 error [for this component] in the DevTools console.

Components with External Assets

Another option let’s us load HTML and styles from external files by using the URLs in the metadata configuration block. This is a common practice to split a component’s code, HTML, and CSS into three separate files in the same directory:

  • header.component.ts
  • header.component.html
  • header.component.css

This practice of using external files is especially important when your HTML or CSS is non-trivial. Using external files keeps your *.ts logic files much cleaner and easier to maintain.

header.component.ts

import { Component, OnInit } from '@angular/core';  @Component({   selector   : 'contacts-header',   templateUrl: 'header.component.html',   styleUrls  : ['header.component.css'] }) export class HeaderComponent implements OnInit { }

header.component.css

.navbar-fixed {   position:fixed; }

header.component.html

<nav class="navbar-fixed">   <div class="nav-wrapper">     <span class="brand-logo center">Contacts</span>   </div> </nav>

Now this is where it gets tricky!

The URL required is relative to the application root which is usually the location of the index.html web page that hosts the application. So the above component must be stored in the application root. Wow, this does not scale well.

  • What if we have many components all at the root level ? ( OMG! )
  • What if my components are organized in distinct packages ?
  • What if we want to organize our components by feature (Angular Style Guide – Best Practice)?

To explore the issue, let’s consider the scenario where our details component (and files) are in the src/app/header package. The urls used above would cause the loader to fail and the developer would see the following 404 error in the developers console:

Component-Relative paths in Angular 2

Are you tempted to try and debug or introspect the exception stack and determine why the file was not found: good luck with that!

So if path to the component HTML or CSS file is not valid, the EASY workaround is to add absolute paths to the URLs… but don’t do it:

header.component.ts

import { Component, OnInit } from '@angular/core';  @Component({   selector   : 'contacts-header',   templateUrl: 'src/app/header/header.component.html',   styleUrls  : ['src/app/header/header.component.css'] }) export class HeaderComponent implements OnInit { }

This is a horrible idea and band-aid solution.

  • What if we move our components to other packages? We will have to update the absolute paths…
  • What if we want to reuse our components in other applications?
  • Can we not use using relative-paths in our components ?

Components with Relative-Path URLs

In fact, we can use relative paths. But not the way you may first think… and not without understanding and accepting some constraints.

At first we might be tempted to try this:

header.component.ts

import { Component, OnInit } from '@angular/core';  @Component({   selector   : 'contacts-header',   templateUrl: './header.component.html',   styleUrls  : ['./header.component.css'] }) export class HeaderComponent implements OnInit { }

We might expect that ./header.component.html is a path relative to the header.component.ts file and that component-relative paths should work, right? Instead, we get another 404 Not found Exception .

Remember we noted that the paths are relative to the Application Root at load time ? Since we are using the package src/app/header/header.component.* , then our files obviously are not at the app root. We could use a gulp or grunt task to deploy to a dist directory and have all our components in the dist root directory.

OMG, that is a horrible idea! Don’t do it.

Component-Relative paths in Angular 2

Why Component-Relative Paths are not supported

At first this limitation seems like a real screw-up in the Angular project. But the bright minds at Google know [from experience] that developers can [and will] load the files and modules using many different methods:

  • Loading each file explicitly using <script type="text/javascript" src="..."></script>
  • Loading from CommonJS packages
  • Loading from SystemJS
  • Loading using JSPM
  • Loading using WebPack
  • Loading using Browserify

There are so many ways developers can deploy their apps, bundled or unbundled, different module formats… It simply is not possible for Angular to absolutely know where the files reside at runtime.

Agreeing on Constraints

If we decide on CommonJS formats AND we use a standard module loader, then we can use the module.id variable which contains the absolute URL of the component class [when the module file is actually loaded]. The exact syntax is moduleId : module.id .

Let’s see how this works in the component:

header.component.ts

import { Component, OnInit } from '@angular/core';  @Component({   moduleId: module.id,    // fully resolved filename; defined at module load time   selector: 'contacts-header',   templateUrl: 'header.component.html',   styleUrls: ['header.component.css'] }) export class HeaderComponent implements OnInit { }

Note: the above requires that your tsconfig.json file specifies commmonjs ; since module.id is a variable available when using that module format: tsconfig.json

{   "compilerOptions": {     "module": "commonjs",     "target": "es5"   } }

JSPM

If we decide to use JSPM , we use alternate format in the config.js file:

config.js

SystemJS.config({   typescriptOptions: {     module: "commonjs",     emitDecoratorMetadata: true,     experimentalDecorators: true   },   transpiler: false,   baseURL: "/dist",   map: {     app: 'src',     typescript: 'node_modules/typescript/lib/typescript.js',     angular2: 'node_modules/angular2',     rxjs: 'node_modules/rxjs'   },   packages: {     app: {       defaultExtension: 'ts',       main: 'app.ts'     },     angular2: {       defaultExtension: 'js'     },     rxjs: {       defaultExtension: 'js'     }   } });

Note: : this solution requires the SystemJS Typescript Plugin to transcompile the typescript

WebPack

If we decide to use WebPack to bundle our files, we can use template : require('./header.component.html') to reference component-relative paths. See WebPack : An Introduction for more details.

header.component.js

import { Component } from '@angular/core';  import '../../public/css/styles.css';  @Component({   selector: 'my-app',   template: require('./header.component.html'),   styles: [require('./header.component.css')] }) export class HeaderComponent implements OnInit { }

Note: here we are not using Urls and we are using template: require('...')

Conclusion

So just remember that setting moduleId : module.id in the @Component decorator is the key lesson here; otherwise Angular 2 will look for our files in paths relative to the application root.

The beauty of this solution is that we can (1) easily repackage our components and (2) easily reuse components… all without changing the @Component metadata.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Component-Relative paths in Angular 2

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址