How do I make an Angular directive usable only by one component? - javascript

The only way I know of being able to use my directive is by exporting it in the module.
#NgModule({
imports: [
CommonModule
],
declarations: [BreadcrumbsComponent, IsRightDirective],
exports: [BreadcrumbsComponent, IsRightDirective]
})
export class BreadcrumbsModule { }
My BreadcrumbsModule is imported by my AppModule
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BreadcrumbsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now when I use my breadcrumbs component, which I named the selector bulma-breadcrumbs, and add the attribute, is-right, it works as expected. However if I add it to another tag, like an h1, the directive also affects it.
I'm trying to get the directive to only apply on the BreadcrumbsComponent.

Before Angular 2 RC5, a hierarchy of directives/components was transparent, because components had directives property that defined components/directives that affected only this component and its children.
After the introduction of NgModule, this feature remained intact but became less evident. As explained in this answer, it's possible with proper hierarchy of modules.
Most times module declarations and exports are same, this allows to use module directives, components and pipes globally within the application.
If a unit isn't exported from a module, it's available only locally, to other units within same module.
This
#NgModule({
imports: [
CommonModule
],
declarations: [BreadcrumbsComponent, IsRightDirective],
exports: [BreadcrumbsComponent]
})
export class BreadcrumbsModule { }
will prevent is-right attribute directive from being compiled anywhere but this module declarations (i.e. BreadcrumbsComponent).
Alternatively, directive selector can be restricted to bulma-breadcrumbs[is-right]. The result will be same, but this won't prevent the directive from being used in other modules that have their local bulma-breadcrumbs component.

You can make the directive effective only if the element tagName is bulma-breadcrumbs:
export class IsRightDirective {
constructor(private elementRef: ElementRef) {
let element = elementRef.nativeElement as HTMLElement;
if (element.tagName.toLowerCase() === "bulma-breadcrumbs") {
element.style.border = "solid 4px red";
...
}
}
}
See this stackblitz for a working example of the code.

Related

NestJS - how to provide services of dynamic modules

I'm struggling to figure out on how to provide services from DynamicModule to regular Modules. Pseudocode below:
app.module.ts
#Global()
#Module({
imports: [
DynamicModule.forRoot(config),
RegularModule,
],
providers: [],
exports: [],
})
export class AppModule {}
dynamic.module.ts
#Module({})
export class DynamicModule implements OnModuleInit, OnModuleDestroy {
constructor(private dynamicService: dynamicService) {}
static forRoot(config: Config): DynamicModule {
return {
module: DynamicModule,
imports: [],
providers: [
{
provide: CONFIG_TOKEN,
useValue: config,
},
DynamicService,
],
exports: [
DynamicService,
],
};
}
}
dynamic.service.ts
#Injectable()
export class DynamicService {
constructor(
#Inject(CONFIG_TOKEN) private readonly config: Config,
) {}
}
regular.module.ts
#Module({
imports: [],
providers: [RegularService, DynamicService],
exports: [RegularService],
})
export class RegularModule {}
regular.service.ts
#Injectable()
export class RegularService {
constructor(
private readonly dynamicService: DynamicService
) {}
}
Providing DynamicService to RegularModule requires to provide CONFIG_TOKEN in RegularModule as well, which seems odd and not practical in case more modules would depend on DynamicService and doesn't seem to be the correct way.
What concepts am I missing and what is correct approach to use services of a DynamicModule?
Would something as forFeature in DynamicModule method would be the right direction?
dynamic modules are modules that need some sort of context defined input, classic example is a database module that would need at least the host url and credentials, which would vary by app.
This means that when the forRoot(input) returns, you have a module just like any regular (non dynamic) module. As such, you can make use of the config value inside the dynamic module's service, export the service on the dynamic module, and then inject that service on other modules that import your dynamic module.
There is no need to also inject the config value on the service that injected the dynamicService.
If you need to have direct access to the config value inside of regularService, and that value is being shared across multiple services and modules, then you should take a look at the ConfigModule and treat that config value as env. If by some very specific reason it cant or should not be in env, then still you should create a separate module for providing this config values.

Splitting the shared module into multiple child modules

Inside of my SharedModule I want to split up related pieces of code into multiple child modules. When I do that however, I lose the common imports from the shared module. For example, I import the FormsModule before my SharedChildModule, but when I try to use ngModel inside my SharedChildModule it tells me that ngModel isn't available. Is it possible to have child modules inside the shared module and also to have those modules inherit the modules from the SharedModule?
When I include the components and directives from ChildSharedModule directly in the SharedModule I have no issues. It is only when I attempt to move those into a child module and then import the whole module into SharedModule that I get the errors.
#NgModule({
declarations: [],
exports: [
CommonModule,
FormsModule,
ChildSharedModule
],
imports: [
CommonModule,
FormsModule,
ChildSharedModule
]
})
export class SharedModule { }
#NgModule({
declarations: [
ChildValidatorDirective,
ChildControlComponent
],
exports: [
ChildValidatorDirective,
ChildControlComponent
]
})
export class SharedChildModule {}
You can not push down dependencies of modules, but you can export modules upwards.
#NgModule({
declarations: [],
exports: [
ChildSharedModule
],
imports: [
ChildSharedModule
]
})
export class SharedModule { }
#NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
ChildValidatorDirective,
ChildControlComponent
],
exports: [
ChildValidatorDirective,
ChildControlComponent,
CommonModule,
FormsModule
]
})
export class SharedChildModule {}
Now SharedModule will import SharedChildModule and will receive everything that is exported by that child module. Since SharedModule is also exporting SharedChildModule then parents who import it will also receive CommonModule and FormsModule.
You are basically breaking tree shaking here, and making unit testing very difficult.
Angular will no longer be able to drop unused modules, because you are coupling everything together. It will also become difficult to remove FormsModule from SharedChildModule because you won't know how many parents depend upon it.
You should try to keep your modules as singular and flat as possible, and have each module import only what it specifically needs.
You can export another module when the consumer doesn't know that other module is a requirement.

Angular2 - How module is different from component?

In the below code from ../src/app/app.module.ts,
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
Component consists:
view(../src/app/app.component.html)
logic(../src/app/app.component.ts)
style(../src/app/app.component.css)
Angular application is a tree of components. Good components have high cohesion, i.e. each component contains only elements with related functionality. They are also well encapsulated and loosely coupled.
How modules are different from components?
A component is just a class with the #Component() annotation. Note that .html and .css files might be referenced by the component, certainly not mandatory. The component template might very well be 'inlined' directly in the component configuration, or there simply might not be any html template at all for a given component.
A module is a structural element of an Angular application (and maybe other classes and interfaces). It is also "just a class" with the #NgModule() annotation.
It acts as a logical 'container' for your components, directives, services, pipes, etc... to help you structure your overall source code better.
You can have a look at this existing question : What's the difference between an Angular component and module
A module is something that has components. It wraps them up so you can import and manage them.
Notice when you make a component you can put anything that's decorated as #Injectable in your constructor:
#Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
constructor(private myService: MyService) { }
ngOnInit() {
}
}
And magically you will have a myService to use. This is dependency injection, which is built into Angular - but it's managed on a Module level. In your module you import what other modules you want to be able to use:
imports: [
BrowserModule,
FormsModule
],
define what your module includes:
declarations: [
AppComponent,
HeroesComponent,
MyService
],
export any components (so other modules can import them)
exports: [
HeroesComponent
],
They help organize an application into blocks of functionality. Components are things that tell angular how to render something. Modules compose Components, Pipes, Services etc into 'blocks' that can be compiled by angular or imported and used by others.
Edit to address comment
Taking your specific question about HttpClient. The HttpClient is the service you are using to perform the actions. The HttpClientModule is the module you import into your module, so you can use the service it contains.
You import the module:
#NgModule({
imports: [
BrowserModule,
// Include it under 'imports' in your application module
// after BrowserModule.
HttpClientModule,
],
})
And use the service:
#Component(...)
export class MyComponent implements OnInit {
// Inject HttpClient into your component or service.
constructor(private http: HttpClient) {}
...
}
The HttpClientModule contains within it all you need for the HttpClient to work, and packages it up so you can use it in your own projects.
This particular module only wraps up that one service, but the module could contain a bunch of related services, components, pipes or directives. For example, the RouterModule allows you to use the RouterOutlet and RouterLink directives.
Module in angular is set of Components, Services, Filters, or some another smaller modules too, or we can say where you import all these in order to use later in the app for future use. in a single app there can be one or more than one module may exist.
Whereas, A component controls a patch of screen called a view.
You define a component's application logic—what it does to support the view—inside a class. The class interacts with the view through an API of properties and methods.
Refer this guide for more details:
https://angular.io/guide/architecture

What is the exact meaning of export keyword in Angular 2\TypeScript?

I am pretty new in Angular 2. I am studying how to create modules into an Angular app and I have the following doubt related a tutorial that I am following.
My doubt is related to the routing.
So in my example there is defined this AuthModule module:
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { SigninComponent } from './signin/signin.component';
import { SignupComponent } from './signup/signup.component';
import { AuthRoutingModule } from './auth-routing.module';
#NgModule({
// Components and directives used by the module:
declarations: [
SigninComponent,
SignupComponent
],
// Import modules used by this features module:
imports: [
FormsModule,
AuthRoutingModule
]
})
export class AuthModule {}
and I have the related rotues configuration class defined:
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { ShoppingListComponent } from './shopping-list/shopping-list.component';
const appRoutes: Routes = [
{ path: '', redirectTo: '/recipes', pathMatch: 'full' },
{ path: 'shopping-list', component: ShoppingListComponent }
];
#NgModule({
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
So I think that the export keyword means that the content related to this class can be exported and used somewhere else (in this case I think into the imports array of the AuthModule class).
Is it? Or am I missing something? What it the exact meaning of the export statment?
I am not understanding if it is something related to Angular or more generally to TypeScript (because here I found https://www.typescriptlang.org/docs/handbook/modules.html). So it seems to me that this module concept is not directly bounded to Angular 2 framework but is a TypeScript concept to subdivide our code in a smart way (then Angular 2 can use this kind of feature of the language).
Is it or am I missing something?
Angular imports/exports and TypeScript imports/exports are two different concepts.
TypeScript imports/exports work at language level to make it clear what
a used identifier references exactly. This is entirely unrelated to Angular.
So, if you use FormsModule there can't be any ambiguity, what FormsModule is meant. If there is more than one FormsModule in your code or any of your dependencies, then you need to make it clear with imports which one is meant. You can't import 2 FormsModule from different locations without disambiguation (for example using as foo in the import and then reference it using foo.FormsModule).
This way you can use code from arbitrary 3rd-party libraries and avoid name collisions.
Angular imports/exports are used to make the content of one module available to be used in another module.
Your:
imports: [
FormsModule,
AuthRoutingModule
]
Allows you to use the directives from FormsModule and AuthRoutingModule in AuthModule and registers the services provided by these modules in the AppModule scope or the closed lazy-loaded root scope.
If you reference any of Angulars directives or services in TypeScript code, you also need to add TypeScript imports. Above FormsModule and AuthRoutingModule need to be imported with TypeScript imports, to make the Angular imports: [...] work.
For example like
<form #f="ngForm">
<input type="text">
</form>
works only if FormsModule is listed in imports: [ ... ] of your current module.
There is no TypeScript import required, because there is no TypeScript code.
Yes you are right by using export keyword before your typescript class you can use that class somewhere else .. in your project

Force service instantiation in Angular 2 sub-module (an alternative to AngularJS run block)

I have a service in sub-module that wraps some third-party module, instantiates and initializes its service to prepare for use within app.
#Injectable()
class SubmoduleInitializerService {
constructor (thirdPartyService: ThirdPartyService) {
thirdPartyService.initialize(...);
...
}
}
#NgModule({
imports: [ThirdPartyModule],
exports: [ThirdPartyModule],
providers: [
ThirdPartyService,
SubmoduleInitializerService
]
})
class AppSubmodule {}
ThirdPartyService isn't injected in app directly but is used by other ThirdPartyModule units, so as long as SubmoduleInitializerService is injected in the same injector as ThirdPartyService or parent injector, everything is fine:
export class AppComponent {
constructor(
/* DO NOT REMOVE! BAD THINGS HAPPEN! */
submoduleInitializerService: SubmoduleInitializerService
) {}
...
}
It was proven to be a lousy pattern because it is not obvious why SubmoduleInitializerService should stay injected in AppComponent if it's not used neither in class nor in template (was accidentally removed once already).
Basically AppSubmodule module needs an alternative to Angular 1.x angular.module(...).run(...) block.
What are the options here?
APP_INITIALIZER (undocumented) service plays the role of AngularJS config/run blocks reasonably well in Angular 2 (not counting the feature of asynchronous initialization).
For noop intialization block that just eagerly instantiates SubmoduleInitializerService it is:
#NgModule({
imports: [ThirdPartyModule],
exports: [ThirdPartyModule],
providers: [
ThirdPartyService,
SubmoduleInitializerService,
{
provide: APP_INITIALIZER,
useFactory: () => () => {},
deps: [SubmoduleInitializerService],
multi: true
}
]
})
class AppSubmodule {}
Since APP_INITIALIZER is multi-provider, it allows to have several initialization functions per application that follow the order in which the modules are being loaded.
For synchronous initialization the shorter (and probably more appropriate) alternative is to inject the service into module's constructor:
#NgModule({
imports: [ThirdPartyModule],
exports: [ThirdPartyModule],
providers: [
ThirdPartyService,
SubmoduleInitializerService
]
})
class AppSubmodule {
constructor(sis: SubmoduleInitializerService) {}
}
As explained in this answer, APP_INITIALIZER shares some traits with config block, too, because it is used to configure services prior to component initialization and is susceptible to race conditions (for example, since APP_INITIALIZER is used to configure Router, injecting it into another APP_INITIALIZER will result in circular dependency).

Categories