diff --git a/cms/articles/1474380939/_article-1474380939.component.js b/cms/articles/1474380939/_article-1474380939.component.js new file mode 100644 index 0000000..ae337b7 --- /dev/null +++ b/cms/articles/1474380939/_article-1474380939.component.js @@ -0,0 +1,448 @@ +import { Component } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import highlight from 'highlight.js'; + +import { xblogTableContentService } from 'xblog-cores/modules'; +import { resourceUtils } from 'xblog-cores/utils'; + + +export var article1474380939Component = Component({ + selector: 'article', + templateUrl: './templates/article-1474380939.html', + host: { + '[class.xblog-article-1474380939]': 'true' + } +}) +.Class({ + constructor: [ + DomSanitizer, + xblogTableContentService, + + function (sanitizer, tableContentService){ + this.id = 1474380939; + this.sanitizer = sanitizer; + this.tableContentService = tableContentService; + } + ], + + ngOnInit: function() { + this.tableContents = this.tableContentService + .getBuilder() + .addHeadings([ + { id: 'injector-providers', name: 'Injector providers' }, + { id: 'class-provider', name: 'Class provider' }, + { id: 'value-provider', name: 'Value provider' }, + { id: 'factory-provider', name: 'Factory provider' }, + { id: 'optional-dependencies', name: 'Optional dependencies' }, + { id: 'dependency-visibility', name: 'Dependency visibility' }, + { id: 'restricting-dependency-lookup', name: 'Restricting dependency lookup' }, + ]) + .build(); + + this.simpleDI = { + sourceCode: { + name: 'Simple DI', + link: resourceUtils.getGithubArticleFileLink(this.id, 'simple-di') + }, + codeBlocks: { + 1: this.getCodeBlock(getSimpleDI) + }, + screenCaptures: { + 1: resourceUtils.getImg('simpleDI-example-1474380939.png') + } + }; + + this.classProvider = { + sourceCode: { + name: 'Class provider', + link: resourceUtils.getGithubArticleFileLink(this.id, 'class-provider') + }, + codeBlocks: { + 1: this.getCodeBlock(getClassProvider01), + 2: this.getCodeBlock(getClassProvider02), + 3: this.getCodeBlock(getClassProvider03) + }, + screenCaptures: { + 1: resourceUtils.getImg('classProvider-example-1474380939.png') + } + }; + + this.valueProvider = { + sourceCode: { + name: 'Value provider', + link: resourceUtils.getGithubArticleFileLink(this.id, 'value-provider') + }, + codeBlocks: { + 1: this.getCodeBlock(getValueProvider01), + 2: this.getCodeBlock(getValueProvider02) + } + }; + + this.factoryProvider = { + sourceCode: { + name: 'Factory provider', + link: resourceUtils.getGithubArticleFileLink(this.id, 'factory-provider') + }, + codeBlocks: { + 1: this.getCodeBlock(getFactoryProvider) + } + }; + + this.optionalDependency = { + sourceCode: { + name: 'Optional dependency', + link: resourceUtils.getGithubArticleFileLink(this.id, 'optional-dependency') + }, + codeBlocks: { + 1: this.getCodeBlock(getOptionalDependency) + } + }; + + this.hostDependency = { + sourceCode: { + name: 'Host dependency', + link: resourceUtils.getGithubArticleFileLink(this.id, 'host-dependency') + }, + codeBlocks: { + 1: this.getCodeBlock(getHostDependency) + } + }; + + this.dependencyVisibility = { + sourceCode: { + name: 'Dependency visibility', + link: resourceUtils.getGithubArticleFileLink(this.id, 'dependency-visibility') + }, + codeBlocks: { + 1: this.getCodeBlock(getDependencyVisibility) + }, + screenCaptures: { + 1: resourceUtils.getImg('dependencyVisibility-example-1474380939.png') + } + }; + }, + + getCodeBlock: function(getter, lang) { + var _langs = lang ? [ lang ] : ['javascript', 'html', 'css']; + + var _codeBlock = highlight.highlightAuto(getter().replace('\n', '').replace(/^ /gm, ''), _langs).value; + + return this.sanitizer.bypassSecurityTrustHtml(_codeBlock); + } +}); + +function getSimpleDI(){ + return ` + import * as ngCore from '@angular/core'; + + export var logService = ngCore.Class({ + constructor: function(){ + this.name = 'logService'; + }, + + setName: function(name){ + this.name = name; + } + }); + + /*---------------------------------------------------------*/ + + export var todoItemComponent = ngCore.Component({ + ..... + providers: [ logService ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoItemComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + directives: [ todoItemComponent ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoListComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + providers: [ logService ], + directives: [ todoListComponent, todoItemComponent ] + }) + .Class({ + constructor: [logService, function(logService){ + logService.setName('logService is hosted by todosComponent'); + }] + });`; +} + +function getClassProvider01(){ + return ` + ngCore.Component({ + ..... + providers: [{ provide: logSerivce, useClass: logSerivce }] + }) + .Class(.....);`; +} + +function getClassProvider02(){ + return ` + export var todoListComponent = ngCore.Component({ + ..... + providers: [ + { provide: logService, useClass: supperLogService } + ] + }) + .Class(.....);`; +} + +function getClassProvider03(){ + return ` + import * as ngCore from '@angular/core'; + + export var rootLogService = ngCore.Class({ + constructor: function(){} + }); + + /*---------------------------------------------------------*/ + + export var supperLogService = ngCore.Class({ + constructor: function(){ + this.name = 'supperLogService'; + } + }); + + /*---------------------------------------------------------*/ + + export var todoItemComponent = ngCore.Component({ + ..... + }) + .Class({ + constructor: [rootLogService, function(logService){ + console.log('todoItemComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + directives: [ todoItemComponent ], + providers: [ + { provide: logService, useClass: supperLogService } + ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoListComponent', logService.name + ' is hosted by todoListComponent'); + }] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + directives: [ todoListComponent, todoItemComponent ], + providers: [ + logService, + { provide: rootLogService, useExisting: logService } + ] + }) + .Class({ + constructor: [logService, function(logService){ + logService.setName('logService is hosted by todosComponent'); + }] + });`; +} + +function getValueProvider01(){ + return ` + export var supperLogService = { + name: 'supperLogService' + }; + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + providers: [ + { provide: logService, useValue: supperLogService } + ] + }) + .Class(.....);`; +} + +function getValueProvider02(){ + return ` + import { OpaqueToken } from '@angular/core'; + + export var WINDOW = new OpaqueToken('window'); + + export var WINDOW_PROVIDERS = [ + { provide: WINDOW, useValue: window } + ]; + + /*---------------------------------------------------------*/ + + import { WINDOW } from './window.model'; + + export var logService = ngCore.Class({ + constructor: [ + ngCore.Inject(WINDOW), + + function(window){ + this.window = window; + } + ], + + log: function(text){ + this.window.console.log(text); + } + }); + + /*---------------------------------------------------------*/ + + import { WINDOW_PROVIDERS } from './window.model'; + + export var todosComponent = ngCore.Component({ + ..... + providers: [ + logService, + WINDOW_PROVIDERS + ] + }) + .Class({ + constructor: [logService, function(logService){ + logService.log('This message is logged by using window.console via DI'); + }] + });`; +} + +function getFactoryProvider(){ + return ` + export var userService = ngCore.Class({ + ..... + setAuth: function(isAuth){ + this.isAuth = isAuth; + } + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + providers: [ + { + provide: logService, + useFactory: function(userService){ + return userService.isAuth ? new supperLogService() : new logService(); + }, + deps: [userService] + } + ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoListComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + providers: [ userService ] + }) + .Class({ + constructor: [userService, function(userService){ + this.userService.setAuth(true); + }] + });`; +} + +function getOptionalDependency(){ + return ` + export var todoListComponent = ngCore.Component({.....}) + .Class({ + constructor: [ + [ new ngCore.Optional(), ngCore.Inject(logService) ], + + function(logService){ + if(!logService) { + console.log('todoListComponent', 'logService is null'); + } + } + ] + });`; +} + +function getHostDependency(){ + return ` + export var todoItemComponent = ngCore.Component({.....}) + .Class({ + constructor: [ + [ new ngCore.Host(), ngCore.Inject(logService) ], + + function(logService){ + console.log('todoItemComponent', logService.name); + } + ] + });`; +} + +function getDependencyVisibility(){ + return ` + export var todoTitleComponent = ngCore.Component({.....}) + .Class({ + constructor: [logService, function(logService){ + console.log('todoTitleComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoItemComponent = ngCore.Component({.....}) + .Class({ + constructor: [logService, function(logService){ + console.log('todoItemComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + template: [ + '', + '' + ].join(''), + viewProviders: [ + { provide: logService, useClass: supperLogService } + ] + }) + .Class({ + constructor: [logService, function(logService){}] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + template: [ + '', + '', + '' + ].join(''), + providers: [ logService ] + }) + .Class({ + constructor: [logService, function(logService){}] + });`; +} \ No newline at end of file diff --git a/cms/articles/1474380939/article-1474380939.component.js b/cms/articles/1474380939/article-1474380939.component.js new file mode 100644 index 0000000..1baa57c --- /dev/null +++ b/cms/articles/1474380939/article-1474380939.component.js @@ -0,0 +1,448 @@ +import { Component } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import highlight from 'highlight.js'; + +import { xblogTableContentService } from 'xblog-cores/modules'; +import { resourceUtils } from 'xblog-cores/utils'; + + +export var article1474380939Component = Component({ + selector: 'article', + template: "
Table of Contents

Angular ships with its own dependency injection framework and we really can't build an Angular application without it.

An Angular application is a tree of components. Each component instance has its own injector. The tree of components parallels the tree of injectors.

We can re-configure the injectors at any level of that component tree with interesting and useful results.


Consider a simple example about TodosComponent.


{{simpleDI.sourceCode.name}}full code

Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree.


When a component at the bottom requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.

If the component's injector lacks the provider, it passes the request up to its parent component's injector. If that injector can't satisfy the request, it passes it along to its parent component's injector.

The requests keep bubbling up until we find an injector that can handle the request or run out of component ancestors. If we run out of ancestors, Angular throws an error.


Dependencies are singletons within the scope of an injector.


In our example, logSerivce are shared among todosComponent, todoListComponent, todoItemComponent. Because it is hosted by todosComponent which is parents of todoListComponent & todoItemComponent.

todoListComponent can inject logSerivce from todosComponent via its constructor. However, nested injectors at todoItemComponent create their own service instances by using providers to host logService itself.



Injector providers

A provider provides the concrete of a dependency value.

The injector relies on providers to create instances of the services that the injector injects into components and other services.


As you know, we can register service provider or directive provider by using providers & directives in component's declaration.

Look back our example, you can see the way we registered logSerivce in TodosComponent.


But there are many ways that we can configure the injector with alternative providers that can deliver an object that behaves like logSerivce. We'll expore them now.


Class Provider

We wrote the providers array like this: providers: [ logSerivce ]

This is actually a short-hand expression for a provider registration.




The first is the token that serves as the key for both locating a dependency value and registering the provider.

The second is a provider definition object, which we can think of as a recipe for creating the dependency value.


With this expression, it's easy for us specify another class to provide the service.

For example we have another service is superLogService, We can register it for todoListComponent with logSerivce token instead of logSerivce class.




However, lets look back the example that you registered logService in todosComponent then you used useClass to register superLogService in todoListComponent.

So how can todoItemComponent inject logService from todoComponent instead of superLogService from todoListComponent ?


The solution for this case is you should use useExisting with rootLogService token for register in todosComponent.


{{classProvider.sourceCode.name}}full code


Value Provider

Sometimes it's easier to provide a object rather than ask the injector to create it from a class.

We can re-write superLogService as an object then registering it by using useValue like this.




Now, you have seen I used a class as the token such as logService, rootLogService for register, acttualy, there's another way for you to get the same result.

The way I'm mentioning is using OpaqueToken & ngCore.Inject.


{{valueProvider.sourceCode.name}}full code

OpaqueToken is a good solution for us to inject global variables such as window, localstorage into our component.


Factory provider

Sometimes we need to create the dependent value dynamically, based on information we won't have until the last possible moment. This situation calls for a factory provider.


Let's illustrate, we want to implement a logic like that if user is authenticated, superLogService should be injected into our components, otherwise, logSerivce should be used.

Assume that we have userService which provide information about authentication. We re-write declaration for providers as below.


{{factoryProvider.sourceCode.name}}full code

The deps property is an array of provider tokens which our factory requires for injection.


Optional dependencies

As you know, if Angular can't resolve injection for component, it'll throw an exception.

In fact, there're situations, we allow the constructor's arguments to be null. We can tell Angular that the dependency is optional by annotating the constructor argument with Optional.

Of course, your code must be prepared to handle a null value.


{{optionalDependency.sourceCode.name}}full code

Dependency visibility

Well, as we learned, we can use the providers property to define providers for its injector.

However, it turns out that there’s another property viewProviders that basically allows us to do the same thing. What’s the difference between those two them ?


viewProviders allows us to define injector providers that are only available for a component’s view.

Let’s take a closer look at what that means by using our TodosComponent example.


{{dependencyVisibility.sourceCode.name}}full code

As you can see, todoTitleComponent is contentChild of TodoListComponent, todoItemComponent is viewChild of TodoListComponent.

Both todoTitleComponent & todoItemComponent require an instance of the class which is provided via logSerivce token.

How should we do if we expect that todoTitleComponent require logSerivce's instance, todoItemComponent require superLogService's instance ?


With viewProviders we can tell the DI system very specifically, which providers are available to which child injectors.

To make our code work as expected, all we have to do is to make the supperLogService provider of todoListComponent explicitly available only for its viewChild by using viewProviders instead of providers.



Now, whenever a component of todoListComponent's view asks for something of type logSerivce, it’ll get an instance of superLogService as expected.

Other child components from the outside world(contentChild) that ask for the same type, however, they won’t see this provider and will continue with the lookup in the injector tree.

Which means todoTitleComponent now gets an expected instance from another parent injector without even knowing that todoListComponent actually introduces its own provider.


Note that, viewProviders are also only available in components, not in directives. That’s simply because a directive doesn’t have its own view.


Restricting dependency lookup

We'll go on with the example in Dependency visibility subject.

Now, look back again, in the example, todoItemComponent requires logSerivce, follow DI rules, it will receice superLogService which is provided by todoListComponent.

This's so cool, however this can be problematic. Just imagine someone uses our todoItemComponent with their customTodoListComponent which doesn't provide superLogService.

What will happen ? our todoItemComponent will receice logSerivce's instance from another parent injector instead. It's unexpected.


If we need to ensure that superLogService instance is always instatiated by our component’s host or an exception should be thrown. Host dependency is a solution for us.


{{hostDependency.sourceCode.name}}full code

", + host: { + '[class.xblog-article-1474380939]': 'true' + } +}) +.Class({ + constructor: [ + DomSanitizer, + xblogTableContentService, + + function (sanitizer, tableContentService){ + this.id = 1474380939; + this.sanitizer = sanitizer; + this.tableContentService = tableContentService; + } + ], + + ngOnInit: function() { + this.tableContents = this.tableContentService + .getBuilder() + .addHeadings([ + { id: 'injector-providers', name: 'Injector providers' }, + { id: 'class-provider', name: 'Class provider' }, + { id: 'value-provider', name: 'Value provider' }, + { id: 'factory-provider', name: 'Factory provider' }, + { id: 'optional-dependencies', name: 'Optional dependencies' }, + { id: 'dependency-visibility', name: 'Dependency visibility' }, + { id: 'restricting-dependency-lookup', name: 'Restricting dependency lookup' }, + ]) + .build(); + + this.simpleDI = { + sourceCode: { + name: 'Simple DI', + link: resourceUtils.getGithubArticleFileLink(this.id, 'simple-di') + }, + codeBlocks: { + 1: this.getCodeBlock(getSimpleDI) + }, + screenCaptures: { + 1: resourceUtils.getImg('simpleDI-example-1474380939.png') + } + }; + + this.classProvider = { + sourceCode: { + name: 'Class provider', + link: resourceUtils.getGithubArticleFileLink(this.id, 'class-provider') + }, + codeBlocks: { + 1: this.getCodeBlock(getClassProvider01), + 2: this.getCodeBlock(getClassProvider02), + 3: this.getCodeBlock(getClassProvider03) + }, + screenCaptures: { + 1: resourceUtils.getImg('classProvider-example-1474380939.png') + } + }; + + this.valueProvider = { + sourceCode: { + name: 'Value provider', + link: resourceUtils.getGithubArticleFileLink(this.id, 'value-provider') + }, + codeBlocks: { + 1: this.getCodeBlock(getValueProvider01), + 2: this.getCodeBlock(getValueProvider02) + } + }; + + this.factoryProvider = { + sourceCode: { + name: 'Factory provider', + link: resourceUtils.getGithubArticleFileLink(this.id, 'factory-provider') + }, + codeBlocks: { + 1: this.getCodeBlock(getFactoryProvider) + } + }; + + this.optionalDependency = { + sourceCode: { + name: 'Optional dependency', + link: resourceUtils.getGithubArticleFileLink(this.id, 'optional-dependency') + }, + codeBlocks: { + 1: this.getCodeBlock(getOptionalDependency) + } + }; + + this.hostDependency = { + sourceCode: { + name: 'Host dependency', + link: resourceUtils.getGithubArticleFileLink(this.id, 'host-dependency') + }, + codeBlocks: { + 1: this.getCodeBlock(getHostDependency) + } + }; + + this.dependencyVisibility = { + sourceCode: { + name: 'Dependency visibility', + link: resourceUtils.getGithubArticleFileLink(this.id, 'dependency-visibility') + }, + codeBlocks: { + 1: this.getCodeBlock(getDependencyVisibility) + }, + screenCaptures: { + 1: resourceUtils.getImg('dependencyVisibility-example-1474380939.png') + } + }; + }, + + getCodeBlock: function(getter, lang) { + var _langs = lang ? [ lang ] : ['javascript', 'html', 'css']; + + var _codeBlock = highlight.highlightAuto(getter().replace('\n', '').replace(/^ /gm, ''), _langs).value; + + return this.sanitizer.bypassSecurityTrustHtml(_codeBlock); + } +}); + +function getSimpleDI(){ + return ` + import * as ngCore from '@angular/core'; + + export var logService = ngCore.Class({ + constructor: function(){ + this.name = 'logService'; + }, + + setName: function(name){ + this.name = name; + } + }); + + /*---------------------------------------------------------*/ + + export var todoItemComponent = ngCore.Component({ + ..... + providers: [ logService ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoItemComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + directives: [ todoItemComponent ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoListComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + providers: [ logService ], + directives: [ todoListComponent, todoItemComponent ] + }) + .Class({ + constructor: [logService, function(logService){ + logService.setName('logService is hosted by todosComponent'); + }] + });`; +} + +function getClassProvider01(){ + return ` + ngCore.Component({ + ..... + providers: [{ provide: logSerivce, useClass: logSerivce }] + }) + .Class(.....);`; +} + +function getClassProvider02(){ + return ` + export var todoListComponent = ngCore.Component({ + ..... + providers: [ + { provide: logService, useClass: supperLogService } + ] + }) + .Class(.....);`; +} + +function getClassProvider03(){ + return ` + import * as ngCore from '@angular/core'; + + export var rootLogService = ngCore.Class({ + constructor: function(){} + }); + + /*---------------------------------------------------------*/ + + export var supperLogService = ngCore.Class({ + constructor: function(){ + this.name = 'supperLogService'; + } + }); + + /*---------------------------------------------------------*/ + + export var todoItemComponent = ngCore.Component({ + ..... + }) + .Class({ + constructor: [rootLogService, function(logService){ + console.log('todoItemComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + directives: [ todoItemComponent ], + providers: [ + { provide: logService, useClass: supperLogService } + ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoListComponent', logService.name + ' is hosted by todoListComponent'); + }] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + directives: [ todoListComponent, todoItemComponent ], + providers: [ + logService, + { provide: rootLogService, useExisting: logService } + ] + }) + .Class({ + constructor: [logService, function(logService){ + logService.setName('logService is hosted by todosComponent'); + }] + });`; +} + +function getValueProvider01(){ + return ` + export var supperLogService = { + name: 'supperLogService' + }; + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + providers: [ + { provide: logService, useValue: supperLogService } + ] + }) + .Class(.....);`; +} + +function getValueProvider02(){ + return ` + import { OpaqueToken } from '@angular/core'; + + export var WINDOW = new OpaqueToken('window'); + + export var WINDOW_PROVIDERS = [ + { provide: WINDOW, useValue: window } + ]; + + /*---------------------------------------------------------*/ + + import { WINDOW } from './window.model'; + + export var logService = ngCore.Class({ + constructor: [ + ngCore.Inject(WINDOW), + + function(window){ + this.window = window; + } + ], + + log: function(text){ + this.window.console.log(text); + } + }); + + /*---------------------------------------------------------*/ + + import { WINDOW_PROVIDERS } from './window.model'; + + export var todosComponent = ngCore.Component({ + ..... + providers: [ + logService, + WINDOW_PROVIDERS + ] + }) + .Class({ + constructor: [logService, function(logService){ + logService.log('This message is logged by using window.console via DI'); + }] + });`; +} + +function getFactoryProvider(){ + return ` + export var userService = ngCore.Class({ + ..... + setAuth: function(isAuth){ + this.isAuth = isAuth; + } + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + providers: [ + { + provide: logService, + useFactory: function(userService){ + return userService.isAuth ? new supperLogService() : new logService(); + }, + deps: [userService] + } + ] + }) + .Class({ + constructor: [logService, function(logService){ + console.log('todoListComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + providers: [ userService ] + }) + .Class({ + constructor: [userService, function(userService){ + this.userService.setAuth(true); + }] + });`; +} + +function getOptionalDependency(){ + return ` + export var todoListComponent = ngCore.Component({.....}) + .Class({ + constructor: [ + [ new ngCore.Optional(), ngCore.Inject(logService) ], + + function(logService){ + if(!logService) { + console.log('todoListComponent', 'logService is null'); + } + } + ] + });`; +} + +function getHostDependency(){ + return ` + export var todoItemComponent = ngCore.Component({.....}) + .Class({ + constructor: [ + [ new ngCore.Host(), ngCore.Inject(logService) ], + + function(logService){ + console.log('todoItemComponent', logService.name); + } + ] + });`; +} + +function getDependencyVisibility(){ + return ` + export var todoTitleComponent = ngCore.Component({.....}) + .Class({ + constructor: [logService, function(logService){ + console.log('todoTitleComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoItemComponent = ngCore.Component({.....}) + .Class({ + constructor: [logService, function(logService){ + console.log('todoItemComponent', logService.name); + }] + }); + + /*---------------------------------------------------------*/ + + export var todoListComponent = ngCore.Component({ + ..... + template: [ + '', + '' + ].join(''), + viewProviders: [ + { provide: logService, useClass: supperLogService } + ] + }) + .Class({ + constructor: [logService, function(logService){}] + }); + + /*---------------------------------------------------------*/ + + export var todosComponent = ngCore.Component({ + ..... + template: [ + '', + '', + '' + ].join(''), + providers: [ logService ] + }) + .Class({ + constructor: [logService, function(logService){}] + });`; +} \ No newline at end of file diff --git a/cms/articles/1474380939/article-1474380939.component.prebuild.json b/cms/articles/1474380939/article-1474380939.component.prebuild.json new file mode 100644 index 0000000..da22ce5 --- /dev/null +++ b/cms/articles/1474380939/article-1474380939.component.prebuild.json @@ -0,0 +1,4 @@ +{ + "origin": "./cms/articles/1474380939/_article-1474380939.component.js", + "inline": "./cms/articles/1474380939/article-1474380939.component.js" +} \ No newline at end of file diff --git a/cms/articles/1474380939/index.js b/cms/articles/1474380939/index.js new file mode 100644 index 0000000..8bf664b --- /dev/null +++ b/cms/articles/1474380939/index.js @@ -0,0 +1,15 @@ +import { resourceUtils } from 'xblog-cores/utils'; +import { article1474380939Component } from './article-1474380939.component'; + +export var article1474380939 = { + id: 1474380939, + title: 'Dependency Injection In Angular 2', + postedDate: 'Tue Sep 20 2016 21:15:38 GMT+0700 (SE Asia Standard Time)', + author: 'Minh Van', + cover: resourceUtils.getImg('xblog-home-cover.jpg'), + routeLink: resourceUtils.getArticleRouteLink('dependency-injection-in-angular-2-1474380939.html'), + relatedArticles: [], + tags: [], + description: 'An Angular application is a tree of components. Each component instance has its own injector! The tree of components parallels the tree of injectors. We can re-configure the injectors at any level of that component tree with interesting and useful results', + content: article1474380939Component, +}; diff --git a/cms/articles/1474380939/resources/images/classProvider-example-1474380939.png b/cms/articles/1474380939/resources/images/classProvider-example-1474380939.png new file mode 100644 index 0000000..9edbd04 Binary files /dev/null and b/cms/articles/1474380939/resources/images/classProvider-example-1474380939.png differ diff --git a/cms/articles/1474380939/resources/images/dependencyVisibility-example-1474380939.png b/cms/articles/1474380939/resources/images/dependencyVisibility-example-1474380939.png new file mode 100644 index 0000000..c78149f Binary files /dev/null and b/cms/articles/1474380939/resources/images/dependencyVisibility-example-1474380939.png differ diff --git a/cms/articles/1474380939/resources/images/simpleDI-example-1474380939.png b/cms/articles/1474380939/resources/images/simpleDI-example-1474380939.png new file mode 100644 index 0000000..f0d243f Binary files /dev/null and b/cms/articles/1474380939/resources/images/simpleDI-example-1474380939.png differ diff --git a/cms/articles/1474380939/templates/article-1474380939.html b/cms/articles/1474380939/templates/article-1474380939.html new file mode 100644 index 0000000..403262f --- /dev/null +++ b/cms/articles/1474380939/templates/article-1474380939.html @@ -0,0 +1,167 @@ + + +
Table of Contents
+
+
+
+

Angular ships with its own dependency injection framework and we really can't build an Angular application without it.

+

An Angular application is a tree of components. Each component instance has its own injector. The tree of components parallels the tree of injectors.

+

We can re-configure the injectors at any level of that component tree with interesting and useful results.

+
+

Consider a simple example about TodosComponent.

+
+ + {{simpleDI.sourceCode.name}} + full code + + +
+
+

Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree.

+
+

When a component at the bottom requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.

+

If the component's injector lacks the provider, it passes the request up to its parent component's injector. If that injector can't satisfy the request, it passes it along to its parent component's injector.

+

The requests keep bubbling up until we find an injector that can handle the request or run out of component ancestors. If we run out of ancestors, Angular throws an error.

+
+

Dependencies are singletons within the scope of an injector.

+
+

In our example, logSerivce are shared among todosComponent, todoListComponent, todoItemComponent. Because it is hosted by todosComponent which is parents of todoListComponent & todoItemComponent.

+

todoListComponent can inject logSerivce from todosComponent via its constructor. However, nested injectors at todoItemComponent create their own service instances by using providers to host logService itself.

+
+
+
+

Injector providers

+

A provider provides the concrete of a dependency value.

+

The injector relies on providers to create instances of the services that the injector injects into components and other services.

+
+

As you know, we can register service provider or directive provider by using providers & directives in component's declaration.

+

Look back our example, you can see the way we registered logSerivce in TodosComponent.

+
+

But there are many ways that we can configure the injector with alternative providers that can deliver an object that behaves like logSerivce. We'll expore them now.

+
+

Class Provider

+

We wrote the providers array like this: providers: [ logSerivce ]

+

This is actually a short-hand expression for a provider registration.

+
+ + + +
+
+

The first is the token that serves as the key for both locating a dependency value and registering the provider.

+

The second is a provider definition object, which we can think of as a recipe for creating the dependency value.

+
+

With this expression, it's easy for us specify another class to provide the service.

+

For example we have another service is superLogService, We can register it for todoListComponent with logSerivce token instead of logSerivce class.

+
+ + + +
+
+

However, lets look back the example that you registered logService in todosComponent then you used useClass to register superLogService in todoListComponent.

+

So how can todoItemComponent inject logService from todoComponent instead of superLogService from todoListComponent ?

+
+

The solution for this case is you should use useExisting with rootLogService token for register in todosComponent.

+
+ + {{classProvider.sourceCode.name}} + full code + + +
+
+
+
+

Value Provider

+

Sometimes it's easier to provide a object rather than ask the injector to create it from a class.

+

We can re-write superLogService as an object then registering it by using useValue like this.

+
+ + + +
+
+

Now, you have seen I used a class as the token such as logService, rootLogService for register, acttualy, there's another way for you to get the same result.

+

The way I'm mentioning is using OpaqueToken & ngCore.Inject.

+
+ + {{valueProvider.sourceCode.name}} + full code + + +
+
+

OpaqueToken is a good solution for us to inject global variables such as window, localstorage into our component.

+
+

Factory provider

+

Sometimes we need to create the dependent value dynamically, based on information we won't have until the last possible moment. This situation calls for a factory provider.

+
+

Let's illustrate, we want to implement a logic like that if user is authenticated, superLogService should be injected into our components, otherwise, logSerivce should be used.

+

Assume that we have userService which provide information about authentication. We re-write declaration for providers as below.

+
+ + {{factoryProvider.sourceCode.name}} + full code + + +
+
+

The deps property is an array of provider tokens which our factory requires for injection.

+
+

Optional dependencies

+

As you know, if Angular can't resolve injection for component, it'll throw an exception.

+

In fact, there're situations, we allow the constructor's arguments to be null. We can tell Angular that the dependency is optional by annotating the constructor argument with Optional.

+

Of course, your code must be prepared to handle a null value.

+
+ + {{optionalDependency.sourceCode.name}} + full code + + +
+
+

Dependency visibility

+

Well, as we learned, we can use the providers property to define providers for its injector.

+

However, it turns out that there’s another property viewProviders that basically allows us to do the same thing. What’s the difference between those two them ?

+
+

viewProviders allows us to define injector providers that are only available for a component’s view.

+

Let’s take a closer look at what that means by using our TodosComponent example.

+
+ + {{dependencyVisibility.sourceCode.name}} + full code + + +
+
+

As you can see, todoTitleComponent is contentChild of TodoListComponent, todoItemComponent is viewChild of TodoListComponent.

+

Both todoTitleComponent & todoItemComponent require an instance of the class which is provided via logSerivce token.

+

How should we do if we expect that todoTitleComponent require logSerivce's instance, todoItemComponent require superLogService's instance ?

+
+

With viewProviders we can tell the DI system very specifically, which providers are available to which child injectors.

+

To make our code work as expected, all we have to do is to make the supperLogService provider of todoListComponent explicitly available only for its viewChild by using viewProviders instead of providers.

+
+
+
+

Now, whenever a component of todoListComponent's view asks for something of type logSerivce, it’ll get an instance of superLogService as expected.

+

Other child components from the outside world(contentChild) that ask for the same type, however, they won’t see this provider and will continue with the lookup in the injector tree.

+

Which means todoTitleComponent now gets an expected instance from another parent injector without even knowing that todoListComponent actually introduces its own provider.

+
+

Note that, viewProviders are also only available in components, not in directives. That’s simply because a directive doesn’t have its own view.

+
+

Restricting dependency lookup

+

We'll go on with the example in Dependency visibility subject.

+

Now, look back again, in the example, todoItemComponent requires logSerivce, follow DI rules, it will receice superLogService which is provided by todoListComponent.

+

This's so cool, however this can be problematic. Just imagine someone uses our todoItemComponent with their customTodoListComponent which doesn't provide superLogService.

+

What will happen ? our todoItemComponent will receice logSerivce's instance from another parent injector instead. It's unexpected.

+
+

If we need to ensure that superLogService instance is always instatiated by our component’s host or an exception should be thrown. Host dependency is a solution for us.

+
+ + {{hostDependency.sourceCode.name}} + full code + + +
+
\ No newline at end of file diff --git a/cms/articles/index.js b/cms/articles/index.js index e1aafe3..a66c2f6 100644 --- a/cms/articles/index.js +++ b/cms/articles/index.js @@ -13,8 +13,11 @@ import { xblogTableContentService } from 'xblog-cores/modules'; -var _ARTICLES = []; +import { article1474380939 } from './1474380939'; +var _ARTICLES = [ + article1474380939 +]; export var ARTICLE_STORE = _init();