Component-relative paths in Angular JS 2

Việc phát triển dựa trên component là 1 tính năng được yêu thích nhất trong Angular 2. Bạn nên làm quen với việc sử dụng các @Component decorators để tạo các component và thông tin siêu dữ liệu được yêu cầu (required metadata infomation) giống như selectortemplate.

import { Component, OnInit } from '@angular/core';

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

Nếu bạn quen với những khái niệm này và may mắn thì các component của bạn load mà không có bất cứ vấn đề gì. Nhưng có khả năng bạn đã gặp hoặc sẽ sớm gặp các lỗi loading component (conponent-loading errors) khi thực hành với Angular 2

Hãy nói về tại sao điều đó xảy ra và xem cách chúng ta có thể giải quyết những vấn đề như vậy theo 1 cách linh hoạt trước khi đi vào vấn đề thực sự chúng ta muốn giải quyết. Đầu tiên hãy xem 2 kiểu triển khai của component.

Components với Inline Metadata

Với mỗi component Angular 2 mà chúng ta triển khai, chúng ta không chỉ định nghĩa 1 HTML template mà còn định nghĩa CSS styles mà đi cùng với template đó, xác định bất cứ selectors, rules và media query mà chúng ta cần.
1 cách để làm việc này đó là thiết lập styletemplate property trong component metadata. Hãy xem 1 component Header đơn giản dưới đây:

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 {
}

Việc sử dụng component này thì siêu dễ, không có các file phụ thuộc bên ngoài mà tất cả HTML và CSS được định nghĩa inline. Do đó chúng ta không bao giờ gặp [trong the DevTools console] 1 lỗi loading 404 cho component này.

Components với External Assets

1 tính năng component khác mà cho phép chúng ta load các file HTML và CSS từ bên ngoài, sử dụng URLs trong cấu hình metedata. Tái cấu trúc (Refactoring) component của chúng ta vào trong 3 file riêng biết trong cùng package là Best Practice phổ biến trong Angular 2.

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

Việc sử dụng các file bên ngoài (external files) thì đặc biệt quan trọng khi HTML và CSS của bạn phức tạp và việc sử dụng external files giữ cho *.ts logic files sạch sẽ và dễ dàng maintain hơn.

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>

Ở trên chúng ta đã sử dụng URLs để xác định các file HTML và CSS bên ngoài. Chú ý rằng URL được yêu cầu ở trên [trong header.component.ts] thì không phải là đường dẫn tuyệt đối (absolute path). Đường dẫn thì là tương đối với application root (mà thường là vị trí của file index.html)
Component ở trên – không có bất cứ thông tin đường dẫn URL nào. – phải được lưu trong application root để tránh các lỗi 404.

  • Những gì nếu chúng ta có tất cả component tại root level?
  • Những gì nếu các component được tổ chức trong các package khác?
  • Những gì nếu chúng ta muốn tổ chức các component của chúng ta theo tính năng (feature) hay ngữ cảnh (context).

‘Tổ chức theo tính năng’ thì thực sự là Best Practice trong Angular 2

Để khám phá ra vấn đề, hãy xem xét kịch bản nơi mà chi tiết component của chúng ta trong src/app/header package. Các URLs được sử dụng ở trên (header.component.htmlheader.component.css) sẽ gây ra lỗi loading và developer sẽ thấy lỗi 404 sau trong developer console.

Nếu đường dẫn tới file HTML or CSS của component không hợp lệ, cách giải quyết dễ dàng là thêm đường dẫn tuyệt đối tới URLs. Hãy khám phá nhanh ý tưởng này:

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 {
}

Phương pháp này ngay lập tức tạo ra các câu hỏi và mối quan tâm:

  • Những gì nếu ta di chuyển các component tới package khác?
  • Những gì nếu ta muốn tái sử dụng các component trong ứng dụng khác?
  • Chúng ta không thể sử đường dẫn tương đối trong component phải không?

Sử dụng đường dẫn tuyệt đối trong URLs cho các HTML hay CSS trong component là 1 ý tưởng kinh khủng. Don’t do it!

Components với Relative-Path URLs

Trong thực tế, chúng ta có thể sử dụng đường dẫn tương đối. Nhưng không phải cách chúng ta nghĩ đầu tiên và không có sự hiểu biết và chấp nhận 1 số ràng buộc.

Đầu tiên chúng ta đã cố gắng thử điều này:

import { Component, OnInit } from '@angular/core';

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

Chúng ta có thể mong đợi ./header.component.html là đường dẫn tương đối tới file header.component.ts và các đường dẫn tương đối đó làm việc, phải không?

Nhớ rằng chúng ta lưu ý rằng các đường dẫn là tương đối với Application Root (tại thời điểm load)? Vì chúng ta sử dụng package src/app/header/header.component.*, rõ ràng các file thì không ở trong Application Root. Thậm chí tệ hơn là 1 kịch bản nơi mà các file được deploy có cấu trúc khác với nguồn ban đầu.
Chúng ta có thể sử dụng Gulp ỏ Grunt task để deploy tới dist directory và có tất cả component trong thư mục dist root. Đừng deploy tất cả các component của bạn tới Application Root. Đây là 1 ý tưởng kinh khủng khác! Don’t do it.

Tại sao Component-Relative Paths không được hỗ trợ?

Đầu tiên giới hạn này dường giống 1 tính năng mà phá hỏng mọi thứ trong Angular. Nhưng các kỹ sư Google biết rằng có thể load các file và module sử dụng nhiều phương pháp khác nhau:

  • Loading file 1 cách rõ ràng với “ tags
  • Loading từ CommonJs package
  • Loading sử dụng SystemJs
  • Loading sử dụng JSPM
  • Loading sử dụng Webpack
  • Loading sử dụng Browserify

Đồng ý với các ràng buộc (constraints)

Nếu chúng ta quyết định sử dụng CommonJs formats và 1 module loader chuẩn, thì chúng ta có thể sử dụng biến module.id mà chứa URL tuyệt đối của component class [khi module file thực sự được load]: cú pháp chính xác là moduleId: module.id.

Hãy xem cách điều này làm việc với component sử dụng CommonJs, SystemJs, JSPMWebpack


CommonJS

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 {
}

Chú ý: Code ở trên yêu cầu rằng file tsconfig.json của bạn xác định commonjs; vì module.id là 1 biến có sẵn khi sử dụng module format đó:

tsconfig.json

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

SystemJs

Nếu chúng ta sử dụng SystemJs, sử dụng biến __moduleName thay vì biến module.id

header.component.ts

import { Component, OnInit } from '@angular/core';

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

JSPM

Nếu chúng ta sử dụng JSPM, sử dụng typescriptOptions configuration format trong file config.js:

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'
    }
  }
});

Webpack

Nếu chúng ta sử dụng Webpack để bundle các files, chúng ta có thể require or import để bắt Webpack load nội dụng file và gán trực tiếp metadata property. Điều này có nghĩa rằng Webpack đang load nội dung thay vì runtime loader của Angular 2. Hãy xem chi tiết hơn: WebPack : An Introduction
Với Webpack có 3 tuỳ chọn có sẵn để load các file HTML và CSS bên ngoài của component:

1) Sử dụng require() để tham chiếu tới component-relative paths.

import { Component } from '@angular/core';

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

Chú ý rằng ở đây chúng ta không sử dụng các key templateUrl và styleUrls. Thay vào đó chúng ta sử dụng require('...') để load dữ liệu và gán nội dung file tới metadata property (template và styles) trước khi khởi tạo component.

2) Một phương pháp thay thế cho require('...'), chúng ta có thể sử dụng import headerTemplate from './header.component.html';

import { Component } from '@angular/core';

import { Component }  from '@angular/core';
import headerTemplate from './header.component.html';
import headerStyle    from './header.component.css';

@Component({
  selector : 'my-app',
  template : headerTemplate,
  styles   : [headerStyle]
})
export class HeaderComponent implements OnInit {
}

3) Cuối cùng, các developer có thể load templates và styles tại thời điểm chạy bằng việc thêm ./ tại bắt đầu của templateUrl và styleUrls properties mà tham chiếu đến component-relative URLs

import { Component } from '@angular/core';

@Component({
  selector : 'my-app',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit { }

Đằng sau đó, Webpack thực sự gọi require('...') để load template và styles nhưng điều này giúp cho code của chúng ta sạch sẽ và gọn gàng.

Add a Comment

Scroll Up