ANgularJS 2.0+

ANGULAR MODULE FAQS(번역 중)

k9e4h 2016. 11. 14. 17:45

2016.10.10에 업데이트된 Angular 공식 사이트 문서를 번역한 것입니다. 본 번역은 연습용이며 문법적인 오류와 과도한 의역이 있을 수 있습니다.



Answers to frequencly asked questions about @NgModule


FAQs

Angulr Modules은 application이 결합력이 있는 기능 블럭으로 구성하는 것을 도와줍니다. Angular Modules chapter는 개념을 다루고 다양한 예제와 lazy loaded modules에서부터 @NgModule까지 단계적으로 접근합니다.

 그러나, 이 챕터(FAQs)에서는 Angular Module의 설계 및 구현에 대해 많은 개발자들이 물어 본 질문에 대하여 답변합니다.

FAQs를 읽기전에  Angular Modules chapter를 읽고 오세요.

Declarations

  • declarations에 추가해야하는 class는 무엇인가요?
  • declarable은 무엇인가요?
  • declarations에 추가하면 안되는 class는 무엇인가요?
  • 왜 같은 Component를 여러 NgModule의 property에 나열 해야하나요?
  • " Can't bind to 'x' since it isn't a known property of 'y' "의 의미는 무엇인가요?

Imports

  • 내가 import해야 하는것은 무엇인가요?
  • BrowserModule, CommonModule을 import 해야 하나요?
  • 같은 module을 두번 import하면 어떻게 되나요?

Exports

  • 내가 export해야 하는 것은 무엇인가요?
  • 내가 export 하면 안되는 것은 무엇인가요?
  • class와 module을 re-export 할 수 있나요?
  • forRoot의 method에는 무엇이 있나요?

Service Providers

Entry Components

General



declarations에 추가해야하는 class는 무엇인가요? 

선언 가능한 class(components, directives와 pipe)를 declarations 목록에 추가해야 합니다. 이 class들은 정확하게 하나의 module이 선언되어야 합니다. module에 속해있는 class는 해당 모듈에 선언합니다.


declarable은 무엇인가요?

Declarables 는 module의 declarations 목록에 추가 가능한 class type( components, directives와 pipe)입니다. 이것들은 declarations에 추가 가능한 유일한 class입니다.


declarations 에 추가하면 안되는 class는 무엇인가요?

declarable class 만이 declarations 목록에 추가될 수 있습니다.

선언하지 마세요.

  • 다른 module이나 @angular module, 3rd party module에서 이미 선언되어 있는 class.

  • 다른 module에서 import된 directives 배열. 예를 들면  @angular/forms의 FORMS_DIRECTIVES는 선언하지 마세요.

  • module classes

  • service classes

  •  strings, numbers, functions, entity models, configurations, business logic, and helper classes 와 같은 Angular가 아닌  classes 및 objects.

왜 같은 Component를 여러 NgModule의 property에 나열 해야하나요?

우리는 종종 declarations 와 bootstrap에 동시에 나열되어 있는 AppComponent를 봅니다. 우리는 declarationsexports, 그리고 entryComponents에 나열된 HeroComponent를 볼 수 있습니다.

이것은 과도하게 느껴집니다. 그러나 이 property들은 다른 기능을 가지고 있고 한 목록의 member가 다른 목록의 member의 의미를 추론할 수 없습니다.

  • AppComponent 는 bootstrap하지 않고 module에 선언됩니다.
  • AppComponent 는 bootstrap하지만 다른 기능 module에 선언할 수 있습니다.
  • HeroComponent 는 다른 app module로 부터 import 되고 ( 그래서 우리는 선언할 수 없습니다.) 이 module로 re-export 합니다.
  • HeroComponent 는 외부 component의 template에 포함하기 위해 export 하고 pop-up dialog에 동적으로 load 할 수있습니다.

"Can't bind to 'x' since it isn't a known property of 'y' " 의 의미는 무엇인가요?

이 에러는 주로 directive "x"를 선언하는 것을 깜빡했거나 "x"가 속한 module을 import하지 않은 것을 의미합니다. 예를 들어 "x"가 ngModel 일때,  @angular/forms의 FormsModule 를 import하지 않았을 것입니다. 아마 당신은 application sub-module에 "x"를 선언하고 그것을 export하는 것을 잊었을 것입니다.  exports 목록에 추가할 때 까지 "x" class는 다른 module에 보이지 않습니다.

내가 import해야 하는 것은 무엇인가요?

 template에서 참조를 필요로하는 선언가능한 public (exported) class를 import하세요.

이것은 NgIfNgFor 같은 Angular directive에 접근하기 위하여 @angular/commonCommonModule를 import 하는 것을 의미합니다. 직접 또는 다른 module을 re-export하는 것을 통해 import 할 수 있습니다.

components에서 [(ngModel)] 양방향 바인딩 표현이 있을 때 @angular/forms의 FormsModule를 import 하세요.

module의 component가 그들의 components, directives와 pipe를 포함할 때 shared module, feature module을 import하세요.

Only import BrowserModule in the root AppModule.



BrowserModule , CommonModule을 import 해야 하나요?

대부분 browser application의 root application module (AppModule) 은 @angular/platform-browser의  BrowserModule 를 import 해야합니다. BrowserModule 은 browser app을 launch하고 run하기 위한 필수적인 service를 제공합니다.

BrowserModule 는 또한 re-export 합니다. 즉, AppModule의 component가NgIf 와NgFor 같은 모든 응용프로그램의 요구 사항에 대해 Angular directives에 접근 가능합니다.

다른 module에서 BrowserModule 를 import 하지마세요Feature modules 와 lazy loaded modules 은 대신에 CommonModule을 import 합니다. 이 것들은 공통의 directive를 필요로 합니다. 이것들은 app-wide provider를 다시 설치 할 필요가 없습니다.

BrowserModule throws an error if you try to lazy load a module that imports it.

Importing CommonModule also frees feature modules for use on any target platform, not just browsers, a fact of some interest to authors of cross-platform libraries.

같은 module을 두번 import하면 어떻게 되나요?

 그것은 문제 되지 않습니다. 3개의 모듈이 모두 module 'A'를 import 할 경우, module 'A'를 가장 처음 직면했을 때 Angular는 module 'A'가 한번 나타났는지 판단하고 이후 'A'를 다시 import 하지 않습니다.


That's true at whatever level A appears in a hierarchy of imported modules. Module 'B'가 Module 'A'를 import 하고, Module 'C'는  'B'를,  Module 'D' 는 [C, B, A]를 import하면, 'D' triggers the evaluation of 'C' which triggers the evaluation of 'B' which evaluates 'A'. When Angular gets to the 'B' and 'A' in 'D', they're already cached and ready to go.

Angular does not like modules with circular references so don't let Module 'A' import Module 'B' which imports Module 'A'.

What should I export?

Export declarable classes that components in other modules should be able to reference in their templates. These are your public classes. If you don't export a class, it stays private, visible only to other component declared in this module.

You can export any declarable class — components, directives, and pipes — whether it is declared in this module or in an imported module.

You can re-export entire imported modules which effectively re-exports all of their exported classes. A module can even export a module that it doesn't import.

What should I not export?

Do not export

  • Private components, directives, and pipes that you need only within components declared in this module. If you don't want another module to see it, don't export it.

  • Non-declarable objects such as services, functions, configurations, entity models, etc.

  • Components that are only loaded dynamically by the router or by bootstrapping. Such entry components can never be selected in another component's template. There's no harm in exporting them but no benefit either.

  • Pure service modules that don't have public (exported) declarations. For example, there is no point in re-exporting HttpModule because it doesn't export anything. It's only purpose is to add http service providers to the application as a whole.

Can I re-export classes and modules?

Absolutely!

Modules are a great way to selectively aggregate classes from other modules and re-export them in a consolidated, convenience module.

A module can re-export entire modules which effectively re-exports all of their exported classes. Angular's own BrowserModule exports a couple of modules like this:

exports: [CommonModule, ApplicationModule]

A module can export a combination of its own declarations, selected imported classes, and imported modules.

Don't bother re-exporting pure service modules. Pure service modules don't export declarable classes that another module could use. For example, there is no point in re-exporting HttpModule because it doesn't export anything. It's only purpose is to add http service providers to the application as a whole.

What is the forRoot method?

The forRoot static method is a convention that makes it easy for developers to configure the module's provider(s).

The RouterModule.forRoot method is a good example. Apps pass a Routes object to RouterModule.forRoot in order to configure the app-wide Router service with routes. RouterModule.forRoot returns a ModuleWithProviders. We add that result to the imports list of the root AppModule.

Only call and import a .forRoot result in the root application module, AppModule. Importing it in any other module, particularly in a lazy loaded module, is contrary to the intent and is likely to produce a runtime error.

RouterModule also offers a forChild static method for configuring the routes of lazy loaded modules.

forRoot and forChild are conventional names for methods that configure services in root and feature modules respectively.

Angular doesn't recognize these names but Angular developers do. Follow this convention when you write similar modules with configurable service providers.

Why is a service provided in a feature module visible everywhere?

Providers listed in the @NgModule.providers of a bootstrapped module have application scope. Adding a service provider to @NgModule.providerseffectively publishes the service to the entire application.

When we import a module, Angular adds the module's service providers (the contents of its providers list) to the application root injector.

This makes the provider visible to every class in the application that knows the provider's lookup token.

This is by design. Extensibility through module imports is a primary goal of the Angular module system. Merging module providers into the application injector makes it easy for a module library to enrich the entire application with new services. By adding the HttpModule once, every application component can make http requests.

However, this can feel like an unwelcome surprise if you are expecting the module's services to be visible only to the components declared by that feature module. If the HeroModule provides the HeroService and the root AppModule imports HeroModule, any class that knows the HeroService typecan inject that service, not just the classes declared in the HeroModule.

Why is a service provided in a lazy loaded module visible only to that module?

Unlike providers of the modules loaded at launch, providers of lazy loaded modules are module-scoped.

When the Angular router lazy-loads a module, it creates a new execution context. That context has its own injector which is a direct child of the application injector.

The router adds the lazy module's providers and the providers of its imported modules to this child injector.

These providers are insulated from changes to application providers with the same lookup token. When the router creates a component within the lazy loaded context, Angular prefers service instances created from these providers to the service instances of the application root injector.

What if two modules provide the same service?

When two imported modules, loaded at the same time, list a provider with the same token, the second module's provider "wins". That's because both providers are added to the same injector.

When Angular looks to inject a service for that token, it creates and delivers the instance created by the second provider.

Every class that injects this service gets the instance created by the second provider. Even classes declared within the first module get the instance created by the second provider. This can be an unwelcome surprise.

If Module A provides a service for token 'X' and imports a module B that also provides a service for token 'X', then Module A's service definition "wins".

The service provided by the root AppModule takes precedence over services provided by imported modules. The AppModule always wins.

How do I restrict service scope to a module?

When a module is loaded at application launch, its @NgModule.providers have application-wide scope. They are available for injection throughout the application.

Imported providers are easily replaced by providers from another imported module. Such replacement may be by design. It could be unintentional and have adverse consequences.

As a general rule, import modules with providers exactly once, preferably in the application's root module. That's also usually the best place to configure, wrap, and override them.

Suppose a module requires a customized HttpBackend that adds a special header for all Http requests. If another module elsewhere in the application also customizes HttpBackend or merely imports the HttpModule, it could override this module's HttpBackend provider, losing the special header. The server will reject http requests from this module.

Avoid this problem by importing the HttpModule only in the AppModule, the application root module.

If you must guard against this kind of "provider corruption", don't rely on a launch-time module's providers.

Load the module lazily if you can. Angular gives a lazy-loaded module its own child injector. The module's providers are visible only within the component tree created with this injector.

If you must load the module eagerly, when the application starts, provide the service in a component instead.

Continuing with the same example, suppose the components of a module truly require a private, custom HttpBackend.

Create a "top component" that acts as the root for all of the module's components. Add the custom HttpBackend provider to the top component's providers list rather than the module's providers. Recall that Angular creates a child injector for each component instance and populates the injector with the component's own providers.

When a child of this component asks for the HttpBackend service, Angular provides the local HttpBackend service, not the version provided in the application root injector. Child components will make proper http requests no matter what other modules do to HttpBackend.

Be sure to create module components as children of this module's top component.

You can embed the child components in the top component's template. Alternatively, make the top component a routing host by giving it a <router-outlet>. Define child routes and let the router load module components into that outlet.

Should I add app-wide providers to the root AppModule or the root AppComponent?

Register application-wide providers in the root AppModule, not in the AppComponent.

Lazy-loaded modules and their components can inject AppModule services; they cannot inject AppComponent services.

Register a service in AppComponent providers only if the service must be hidden from components outside the AppComponent tree. This is a rare exceptional use case.

More generally, prefer registering providers in modules to registering in components.

DISCUSSION:

Angular registers all startup module providers with the application root injector. The services created from root injector providers are available to the entire application. They are application-scoped.

Certain services (e.g., the Router) only work when registered in the application root injector.

By contrast, Angular registers AppComponent providers with the AppComponent's own injector. AppComponentservices are available only to that component and its component tree. They are component-scoped.

The AppComponent's injector is a child of the root injector, one down in the injector hierarchy. That is almost the entire application for apps that don't use the router. But "almost" isn't good enough for routed applications.

AppComponent services don't exist at the root level where routing operates. Lazy loaded modules can't reach them. In the Angular Module Chapter sample applications, if we had registered UserService in the AppComponent, the HeroComponent couldn't inject it. The application would fail the moment a user navigated to "Heroes".

Should I add other providers to a module or a component?

In general, prefer registering feature-specific providers in modules (@NgModule.providers) to registering in components (@Component.providers).

Register a provider with a component when you must limit the scope of a service instance to that component and its component tree. Apply the same reasoning to registering a provider with a directive.

For example, a hero editing component that needs a private copy of a caching hero service should register the HeroService with the HeroEditorComponent. Then each new instance of the HeroEditorComponent gets its own cached service instance. The changes that editor makes to heroes in its service do not touch the hero instances elsewhere in the application.

Always register application-wide services with the root AppModule, not the root AppComponent.

Why is it bad if SharedModule provides a service to a lazy loaded module?

This question arose in the Angular Module chapter when we discussed the importance of keeping providers out of the SharedModule.

Suppose we had listed the UserService in the module's providers (which we did not). Suppose every module imports this SharedModule (which they all do).

When the app starts, Angular eagerly loads the AppModule and the ContactModule.

Both instances of the imported SharedModule would provide the UserService. Angular registers one of them in the root app injector (see above). Then some component injects UserService, Angular finds it in the app root injector, and delivers the app-wide singleton UserService. No problem.

Now consider the HeroModule which is lazy loaded!

When the router lazy loads the HeroModule, it creates a child injector and registers the UserService provider with that child injector. The child injector is not the root injector.

When Angular creates a lazy HeroComponent, it must inject a UserService. This time it finds a UserService provider in the lazy module's child injectorand creates a new instance of the UserService. This is an entirely different UserService instance than the app-wide singleton version that Angular injected in one of the eagerly loaded components.

That's almost certainly a mistake.

Prove it for yourself. Run the live example. Modify the SharedModule so that it provides the UserService rather than the CoreModule. Then toggle between the "Contact" and "Heroes" links a few times. The username goes bonkers as the Angular creates a new UserService instance each time.

Why does lazy loading create a child injector?

Angular adds @NgModule.providers to the application root injector ... unless the module is lazy loaded. Then it creates a child injector and adds the module's providers to the child injector.

This means that a module behaves differently depending on whether it is loaded during application start or lazy loaded later. Neglecting that difference can lead to adverse consequences.

Why doesn't Angular add lazy loaded providers to the app root injector as it does for eagerly loaded modules? Why the inconsistency?

The answer is grounded in a fundamental characteristic of the Angular dependency injection system. An injector can add providers until it is first used. Once an injector starts creating and delivering services, its provider list is frozen. No new providers allowed.

When an applications starts, Angular first configures the root injector with the providers of all eagerly loaded modules before creating its first component and injecting any of the provided services. Once the application begins, the app root injector is closed to new providers.

Time passes. Application logic triggers lazy loading of a module. Angular must add the lazy loaded module's providers to an injector somewhere. It can't added them to the app root injector because that injector is closed to new providers. So Angular creates a new child injector for the lazy loaded module context.

How can I tell if a module or service was previously loaded?

Some modules and its services should only be loaded once by the root AppModule. Importing the module a second time by lazy loading a module could produce errant behavior that may be difficult to detect and diagnose.

We can guard against that danger by writing a constructor that attempts to inject the module or service from the root app injector. If the injection succeeds, the class has been loaded a second time. We can throw an error or take other remedial action.

Certain Angular modules (such as BrowserModule) implements such a guard as does this Angular Module chapter sample's CoreModule constructor.

app/core/core.module.ts (Constructor)

constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
  if (parentModule) {
    throw new Error(
      'CoreModule is already loaded. Import it in the AppModule only');
  }
}

What is an entry component?

Any component that Angular loads imperatively by type is an entry component,

A component loaded declaratively via its selector is not an entry component.

Most application components are loaded declaratively. Angular uses the component's selector to locate the element in the template. It then creates the HTML representation of the component and inserts it into the DOM at the selected element. These are not entry components.

A few components are only loaded dynamically and are never referenced in a component template.

The bootstrapped root AppComponent is an entry component. True, its selector matches an element tag in index.html. But index.html is not a component template and the AppComponent selector doesn't match an element in any component template.

Angular loads AppComponent dynamically either because we listed it by type in @NgModule.bootstrap or because we boostrapped it imperatively with the module's ngDoBootstrap method.

Components in route definitions are also entry components. A route definition refers to a component by its type. The router ignores a routed component's selector (if it even has one) and loads the component dynamically into a RouterOutlet.

The compiler can't discover these entry components by looking for them in other component templates. We must tell it about them ... by adding them to the entryComponents list.

Angular automatically adds two kinds of components to the module's entryComponents:

  1. the component in the @NgModule.bootstrap list
  2. components referenced in router configuration

We don't have to mention these components explicitly although it does not harm to do so.

What's the difference between a bootstrap component and an entry component?

A bootstrapped component is an entry component. It's an entry component that Angular loads into the DOM during the bootstrap (application launch) process. Other entry components are loaded dynamically by other means such as with the router.

The @NgModule.bootstrap property tells the compiler both that this is an entry component and that it should generate code to bootstrap the application with this component.

There is no need to list a component in both the bootstrap and entryComponent lists although it is harmless to do so.

When do I add components to entryComponents?

Most application developers won't need to add components to the entryComponents.

Angular adds certain components to entry components automatically. Components listed in @NgModule.bootstrap are added automatically. Components referenced in router configuration are added automatically. These two mechanisms account for almost all entry components.

If your app happens to bootstrap or dynamically load a component by type in some other manner, you'll have to add it to entryComponents explicitly.

Although it's harmless to add components to this list, it's best to add only the components that are truly entry components. Don't include components that are referenced in the templates of other components.

Why does Angular need entryComponents?

Entry components are also declared. Why doesn't the Angular compiler generate code for every component in @NgModule.declarations? Then we wouldn't need entry components.

The reason is tree shaking. For production apps we want to load the smallest, fastest code possible. The code should contain only the classes that we actually need. It should exclude a component that's never used, whether or not that component is declared.

In fact, many libraries declare and export components we'll never use. The tree shaker will drop these components from the final code package if we don't reference them.

If the Angular compiler generated code for every declared component, it would defeat the purpose of the tree shaker.

Instead, the compiler adopts a recursive strategy that generates code only for the components we use.

It starts with the entry components, then it generates code for the declared components it finds in an entry component's template, then for the declared components it discovers in the templates of previously compiled components, and so on. At the end of the process, it has generated code for every entry component and every component reachable from an entry component.

If a component isn't an entry component or wasn't found in a template, the compiler omits it.

WHAT KINDS OF MODULES SHOULD I HAVE AND HOW SHOULD I USE THEM?

Every app is different and developers have varying levels of experience and comfort with the available choices. Some suggestions and guidelines appear to have wide appeal.

The following is preliminary guidance based on early experience using Angular modules in a few applications. Read with appropriate caution and reflection.

SHAREDMODULE

Create a SharedModule with the components, directives, and pipes that you use everywhere in your app. This module should consist entirely of declarations most of them exported.

It may re-export other widget modules such as CommonModuleFormsModule and modules with the UI controls that you use most widely.

It should not have providers for reasons explained earlier. Nor should any of its imported or re-exported modules have providers. Know what you're doing and why if you deviate from this guideline.

Import the SharedModule in your feature modules, both those loaded when the app starts and those you lazy load later.

COREMODULE

Create a CoreModule with providers for the singleton services you load when the application starts.

Import CoreModule in the root AppModule only. Never import CoreModule in any module other than the root AppModule.

Consider making CoreModule a pure services module with no declarations.

This chapter sample departs from that advice by declaring and exporting two components that are only used within the root AppComponentdeclared by AppModule. Someone following this guideline strictly would have declared these components in the AppModule instead.

FEATURE MODULES

Create Feature Modules around specific application business domains, user workflows, and utility collections.

Feature modules tend to fall into one of these four groups:

Real world modules are often hybrids that knowingly deviate from the following guidelines. They are guidelines, not laws. Follow them until you have a good reason to do otherwise.

Feature ModuleGuidelines
Domain

Domain Feature Modules deliver a user experience dedicated to a particular application domain like editing a customer or placing an order.

They typically have a top component that acts as the feature root. Private, supporting sub-components descend from it.

Domain feature module consist mostly of declarations. Only the top component is exported.

Domain feature modules rarely have providers. When they do, the lifetime of the provided services should be the same as the lifetime of the module.

Do not provide application-wide singleton services in a domain feature module.

Domain feature modules are typically imported exactly once by a larger feature module.

They might be imported by the root AppModule of a small application that lacks routing.

For an example, see ContactModule in the Angular Module chapter, before we introduced routing.

Routed

Routed Feature Modules are Domain Feature modules whose top components are the targets of router navigation routes.

All lazy loaded modules are routed feature modules by definition.

This chapter's ContactModuleHeroModule and CrisisModule are routed feature modules.

Routed Feature Modules should not export anything. They don't have to because none of their components ever appear in the template of an external component.

A lazy loaded Routed Feature Module should not be imported by any module. Doing so would trigger an eager load, defeating the purpose of lazy loading. HeroModule and CrisisModule are lazy loaded. They aren't mentioned among the AppModuleimports.

But an eager loaded Routed Feature Module must be imported by another module so that the compiler learns about its components. ContactModule is eager loaded and, therefore, is listed among the AppModule imports.

Routed Feature Modules rarely have providers for reasons explained earlier. When they do, the lifetime of the provided services should be the same as the lifetime of the module.

Do not provide application-wide singleton services in a routed feature module or in a module that the routed module imports.

Routing

Routing Module provides routing configuration for another module.

A Routing Module separates routing concerns from its companion module.

It typically:

  • defines routes
  • adds router configuration to the module's imports
  • re-exports RouterModule
  • adds guard and resolver service providers to the module's providers.

The name of the Routing Module should parallel the name of its companion module, using the suffix "Routing". For example, FooModule in foo.module.ts has a routing module named FooRoutingModule in foo-routing.module.ts

If the companion module is the root AppModule, the AppRoutingModule adds router configuration to its imports with RouterModule.forRoot(routes). All other Routing Modules are children that import RouterModule.forChild(routes).

A Routing Module re-exports the RouterModule as a convenience so that components of the companion module have access to router directives such as RouterLink and RouterOutlet.

A Routing Module should not have its own declarations!   Components, directives, and pipes are the responsibility of the feature module not the routing module.

A Routing Module should only be imported by its companion module.

The AppRoutingModuleContactRoutingModule and HeroRoutingModule are good examples.

Service

Service Modules provide utility services such as data access and messaging.

Ideally they consist entirely of providers and have no declarations. The CoreModule and Angular's HttpModule are good examples.

Service Modules should only be imported by the root AppModule.

Do not import them in other feature modules. Know what you're doing and why if you deviate from this guideline.

Widget

Widget Module makes components, directives, and pipes available to external modules.

CommonModule and SharedModule are widget modules. Many third party UI component libraries are widget modules.

A Widget Module should consist entirely of declarations, most of them exported.

A Widget Module should rarely have providers. Know what you're doing and why if you deviate from this guideline.

Import Widget Modules in any module whose component templates need the widgets.

The following table summarizes the key characteristics of each Feature Module group.

Real world modules are often hybrids that knowingly deviate from these guidelines.

Feature ModuleDeclarationsProvidersExportsImported ByExamples
DomainYesRareTop ComponentFeature, AppModuleContactModule (before routing)
RoutedYesRareNoNobodyContactModuleHeroModuleCrisisModule
RoutingNoYes (Guards)RouterModuleFeature (for routing)AppRoutingModuleContactRoutingModuleHeroRoutingModule
ServiceNoYesNoAppModuleHttpModuleCoreModule
WidgetYesRareYesFeatureCommonModuleSharedModule

What's the difference between Angular and JavaScript Modules?

Angular and JavaScript are two different yet complementary module systems.

In modern JavaScript, every file is a module. Within each file we write an export statement to make parts of the module public:

export class AppComponent { ... }

Then we import a part in another module:

import { AppComponent }  from './app.component';

This kind of modularity is a feature of the JavaScript language.

An Angular Module is a feature of Angular itself.

Angular's NgModule also has imports and exports and they serve a similar purpose.

We import other Angular modules so we can use their exported classes in component templates. We export this Angular module's classes so they can be imported and used by components of other modules.

The Angular module classes differ from JavaScript module class in three key respects:

  1. An Angular module bounds declarable classes only. Declarables are the only classes that matter to the Angular compiler.

  2. Instead of defining all member classes in one giant file (as in a JavaScript module), we list the module's classes in the @NgModule.declarationslist.

  3. An Angular module can only export the declarable classes it owns or imports from other modules. It doesn't declare or export any other kind of class.

The Angular Module is also special in another way. Unlike JavaScript modules, an Angular module can extend the entire application with services by adding providers to the @NgModule.providers list.

The provided services do not belong to the module nor are they scoped to the declared classes. They are available everywhere.

Here's an Angular Module class with imports, exports, and declarations.

@NgModule({
  imports:      [ CommonModule, FormsModule ],
  declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
  exports:      [ ContactComponent ],
  providers:    [ ContactService ]
})
export class ContactModule { }

Of course we use JavaScript modules to write Angular modules as seen in the complete contact.module.ts file:

app/contact/contact.module.ts

import { NgModule }           from '@angular/core';
import { CommonModule }       from '@angular/common';
import { FormsModule }        from '@angular/forms';

import { AwesomePipe }        from './awesome.pipe';

import
       { ContactComponent }   from './contact.component';
import { ContactService }     from './contact.service';
import { HighlightDirective } from './highlight.directive';

@NgModule({
  imports:      [ CommonModule, FormsModule ],
  declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
  exports:      [ ContactComponent ],
  providers:    [ ContactService ]
})
export class ContactModule { }

HOW DOES ANGULAR FIND COMPONENTS, DIRECTIVES, AND PIPES IN A TEMPLATE?
WHAT IS A TEMPLATE REFERENCE?

The Angular compiler looks inside component templates for other components, directives, and pipes. When it finds one, that's a "template reference".

The Angular compiler finds a component or directive in a template when it can match the selector of that component or directive to some HTML in that template.

The compiler finds a pipe if the pipe's name appears within the pipe syntax of the template HTML.

Angular only matches selectors and pipe names for classes that are declared by this module or exported by a module that this module imports.

What is the Angular Compiler?

The Angular Compiler converts the application code we write into highly performant JavaScript code. The @NgModule metadata play an important role in guiding the compilation process.

The code we write is not immediately executable. Consider components. Components have templates that contain custom elements, attribute directives, Angular binding declarations, and some peculiar syntax that clearly isn't native HTML.

The Angular Compiler reads the template markup, combines it with the corresponding component class code, and emits component factories.

A component factory creates a pure, 100% JavaScript representation of the component that incorporates everything described in its @Componentmetadata: the HTML, the binding instructions, the attached styles ... everything.

Because directives and pipes appear in component templates, the Angular Compiler incorporates them into compiled component code too.

@NgModule metadata tells the Angular Compiler what components to compile for this module and how to link this module with other modules.

NgModule API

The following chart summarizes the NgModule metadata properties.

PropertyDescription
declarations

A list of declarable classes, the componentdirective and pipe classes that belong to this module.

These declared classes are visible within the module but invisible to components in a different module unless (a) they are exported from this module and (b) that other module imports this one.

Components, directives and pipes must belong to exactly one module. The compiler emits an error if we try to declare the same class in more than one module.

Do not re-declare a class imported from another module.

providers

A list of dependency injection providers.

Angular registers these providers with the root injector of the module's execution context. That's the application's root injector for all modules loaded when the application starts.

Angular can inject one of these provider services into any component in the application. If this module provides the HeroService, or any module loaded at launch provides the HeroService, Angular can inject the same HeroService intance into any app component.

A lazy loaded module has its own sub-root injector which typically is a direct child of the application root injector.

Lazy loaded services are scoped to the lazy module's injector. If a lazy loaded module also provides the HeroService, any component created within that module's context (e.g., by router navigation) gets the local instance of the service, not the instance in the root application injector.

Components in external modules continue to receive the instance created for the application root.

imports

A list of supporting modules.

Specifically, the list of modules whose exported components, directives or pipes are referenced by the component templates declared in this module.

A component template can reference another component, directive or pipe on two conditions: either the referenced class is declared in this module or the class was imported from another module.

A component can use the NgIf and NgFor directives only because its parent module imported the Angular CommonModule (perhaps indirectly by importing BrowserModule).

We can import many standard directives with the CommonModule. But some familiar directives belong to other modules. A component template can bind with [(ngModel)] only after importing the Angular FormsModule.

exports

A list of declarations — componentdirective, and pipe classes — that an importing module can use.

Exported declarations are the module's public API. A component in another module can reference this module's HeroComponent if (a) it imports this module and (b) this module exports HeroComponent.

Declarations are private by default. If this module does not export HeroComponent, no other module can see it.

Importing a module does not automatically re-export the imported module's exports. Module 'B' can't use ngIf just because it imported module A which imported CommonModule. Module 'B' must import CommonModule itself.

A module can list another module among its exports in which case all of that module's public components, directives, and pipes are exported.

Re-export makes module transitivity explicit. If Module 'A' re-exports CommonModule and Module 'B' imports Module 'A', Module 'B' components can use ngIf even though 'B' itself didn't import CommonModule.

bootstrap

A list of components that can be bootstrapped.

Usually there is only one component in this list, the root component of the application.

Angular can launch with multiple bootstrap components, each with its own location in the host web page.

A bootstrap component is automatically an entryComponent

entryComponents

A list of components that are not referenced in a reachable component template.

Most developers will never set this property. Here's why.

The Angular Compiler must know about every component actually used in the application. The compiler can discover most components by walking the tree of references from one component template to another.

But there's always at least one component that is not referenced in any template: the root component, AppComponent, that we bootstrap to launch the app. That's why it's called an entry component.

Routed components are also entry components because they aren't referenced in a template either. The router creates them and drops them into the DOM near a <router-outlet>.

While the bootstrapped and routed components are entry components, we usally don't have to add them to a module's entryComponents list.

Angular automatically adds components in the module's bootstrap list to the entryComponents list. The RouterModule adds routed components to that list.

That leaves only two sources of undiscoverable components.

  1. Components bootstrapped using one of the imperative techniques.
  2. Components dynamically loaded into the DOM by some means other than the router.

Both are advanced techniques that few developers will ever employ. If you are one of those few, you'll have to add these components to the entryComponents list yourself, either programmatically or by hand.


반응형

'ANgularJS 2.0+' 카테고리의 다른 글

AngularJS에서 *가 의미하는 것  (0) 2017.03.24
angular 4  (0) 2016.12.12
Angular Release Breaking Changes  (0) 2016.11.07
Angular Spring MVC  (0) 2016.10.07
[AngualrJS 2.0] Angular 2.0 Release  (0) 2016.10.04