What's the best way to include templates with AngularJS + Webpack2? - javascript

EDIT: I haven't found a way to import templates with an import statement instead of require, but I have discovered that I can simplify my configuration.
In the webpack config, use html-loader instead of ngtemplate-loader for /\.html$/. Then, in the component, require the template at the top of the file like we did in the original post, but change "templateUrl" to "template" in the component definition. Webpack will do the rest for you.
const template = require('./component.html');
class Example(){
...
}
export const component = {
bindings: {},
controller: Example,
template: template
};
original post:
I have a working solution with ngtemplate-loader:
const template = require('./component.html');
import ExampleService from './example.service';
class Example() {
constructor(private exampleService: ExampleService) {
}
...
}
export const component = {
bindings: {},
controller: Example,
templateUrl: template
};
But I'm afraid my colleagues will object to the require syntax. You see, I am migrating our app from Grunt to Webpack2, and the current app only uses import foo from './bar' syntax.
We also don't import templates in the javascript files in our current build; we use a callback in the templateUrl to return a string.
import ExampleService from './example.service';
class Example() {
constructor(private exampleService: ExampleService) {
}
...
}
export const component = {
bindings: {},
controller: Example,
templateUrl: ['constantsFactory', function(constantsFactory) {
return `${constantsFactory.path}/component.html`;
}]
};
Is there a webpack loader that can populate $templateCache without require?
If not, is there a way to import templates in Typescript with import syntax?

There is not a way to add templates to the $templateCache by using import syntax, but you can add templates to the cache with 'require' syntax.
angular1-templateurl-loader
Best loader I could find right now.

Related

How do I get the entryComponents dynamically using import() method of Typescript 2.4

Typescript 2.4 introduce [import()][1] method to load modules dynamically. I am trying to load components dynamically using the following mentioned procedure https://angular.io/guide/dynamic-component-loader where we need to mention entryComponents in the module.
I am able to load all component modules dynamically using import() method in the AppModule but I am not able to get entryComponents of the modules. For example,
I have a component and its module
data-widget.component:
#Component({
selector: 'data-widget',
templateUrl: 'data-widget.component.html'
})
export class DataWidget{}
and
data-widget.module.ts
#NgModule({
imports: [ ...,
declarations: [DataWidget],
exports: [DataWidget],
providers: [],
entryComponents: [DataWidget]
})
export class DataWidgetModule {
constructor(private widgetService: widgetService) {
widgetService.register('weather', DataWidget);
}
}
I am now able to load that module dynamically using import() method in the following way
app.module.ts
#NgModule({
imports: [ ...,
declarations: [],
exports: [],
providers: []
})
export class AppModule {
constructor(private apiService: apiService) {
apiService.getMoudleUrls().subscribe( data=>{
import(''+data); //e.g './data-widget/data-widget.module'
},
error=>{});
}
}
In AppModule module the DataWidgetModule loaded in Webpack dynamically.Now I am trying to load DataWidget Component dynamically. You can see that I have register the component in DataWidgetModule using widgetService. But this function was not called during module loading as well as the entryComponents are also not loaded. So, when I tried to load the component using the following way which I have mentioned above
this.componentFactoryResolver.resolveComponentFactory(widgetService.getRegisteredWidget());
then I am getting error as entryComponents are not loaded in #ngModule. How can I load the entryComponents and register the components for dynamic load? It will be very helpful to get a solution for it. Thank you.
I don't know if this is the right topic, but I had a similar problem and found a little hacky solution.
The problem was that I wanted to use the entry components of a new compiled module inside the parent module. The entry components of the loaded and compiled module are present only in the compiled module so I had to add them to the parent module entry components.
Just add the entry components factories to the parent component factories.
const resolver = moduleRef.componentFactoryResolver;
resolver._factories.forEach(function (value, key) {
resolver._parent._factories.set(key, value);
});
In my case I had only a few components and entry components in the loaded module so it is no overhead to merge them with the parent component factories. For larger loaded modules I think it would be good to only merge the entry components...
I find a solution for it. The following code will help you to solve this problem.
export class DynamicComponent {
injector: Injector;
compiler: Compiler;
#ViewChild('container', {read: ViewContainerRef})
container: ViewContainerRef;
componentRef: any;
constructor(private compFactoryResolver: ComponentFactoryResolver, injector: Injector,
private apiService: apiService) {
this.injector = ReflectiveInjector.resolveAndCreate(COMPILER_PROVIDERS, injector);
this.compiler = this.injector.get(Compiler);
}
addWidget(){
apiService.getMoudleUrls().subscribe( module_url=>{
let module_= import(module_url); //e.g './data-widget/data-widget.module'
module_.then(module_data =>{
const keys = Object.getOwnPropertyNames( module_data );
let moduleFactories = this.compiler.compileModuleAndAllComponentsSync(module_data[keys[1]]);
const moduleRef = moduleFactories.ngModuleFactory.create(this.injector);
const componentFactory = moduleFactories.componentFactories
.find(e => e.selector === 'data-widget'); // find the entry component using selector
this.componentRef = this.container.createComponent(componentFactory, null, moduleRef.injector);
const newItem: WidgetComponent = this.componentRef.instance;
},
error=>{});
}
}

How do I declare a dependency on an ng-metadata module?

I have a project that is using ng-metadata (https://github.com/ngParty/ng-metadata) to build a handful of Angular 1.5 modules. I have a test module/component that looks like this:
import { NgModule, Component, Inject, Input, Output, EventEmitter } from 'ng-metadata/core'
import { platformBrowserDynamic } from 'ng-metadata/platform-browser-dynamic'
#Component({
selector: 'test',
template: require('./test.template.html')
})
class TestComponent {
#Input() type: string;
constructor() {
console.log(`test: ${this.type}`)
}
}
#NgModule({
declarations: [TestComponent]
})
class HeroModule {}
platformBrowserDynamic().bootstrapModule(HeroModule)
Everything seems happy when compiled and I'm now attempting to use the module in another project (that is not using ng-metadata but has a compatible version of Angular).
I'm simply including the shims as directed by the ng-metadata docs and the JavaScript file that contains the module described above (built by webpack). I have a new module in this project that wants to list the HeroModule as a dependency. I've tried a few things:
// attempt 1:
angular.module('my-consuming-module', ['ui.router', 'hero'])
// attempt 2:
angular.module('my-consuming-module', ['ui.router', 'heroModule'])
// attempt 3:
angular.module('my-consuming-module', ['ui.router', 'hero-module'])
All always end up with the same Error: $injector:nomod Module Unavailable error from Angular.
If I'm using ng-metadata to build my modules, what are the names I use to list them as dependencies in another project?
Finally figured this out! It's amazing what happens when you carefully read documentation...
Found in the Manual Angular 1 Bootstrap section of ng-metadata's docs:
You can still leverage ng2 way of components registration without ng-metadata bootstrap, but you have to manually create your Angular 1 module from an ng-metadata #NgModule using the bundle helper function.
I ended up being able to do the following:
// REMOVED platformBrowserDynamic().bootstrapModule(HeroModule)
const Ng1AdminModule = bundle(HeroModule).name;
export const AppModule = angular.module('hero', [Ng1AdminModule]);
And then my hero module becomes accessible to the my-consuming-module just as I expected. The bundle helper function was the key to figuring this out.
You need to import those module from their respective locations and inject it inside your angular module
//ensure `angular`, `angular-ui-router` should be there in `map` of systemjs.config.js
import * as angular from 'angular';
import * as uiRouter from 'angular-ui-router';
import { heroModule} from './hero.module';
angular.module('app',[ uiRouter, heroModule]);
Check references here

AnguarJS with ES2016 load config module

I'm trying to start a new Angular 1 Application based on ES6. I use webpack and the babel-loader to convert the JS.
My problem now is to load an own config module. Please have a look at this:
// config/config.js
import angular from 'angular';
export default angular.module('config')
.factory('config', () => {
return {
url: {
products: 'https://....'
},
products: []
}
})
The corresponding app.js reads (I stripped some imports):
import angular from 'angular';
import config from './config/config';
import HomeCtrl from './controller/HomeController';
let app = () => {
return {
template: require('./app.html')
}
};
const MODULE_NAME = 'app';
angular.module(MODULE_NAME, [uiRouter, config])
.directive('myapp', app)
.config(['$stateProvider', '$urlRouterProvider', 'config', function ($stateProvider, $urlRouterProvider, config) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state("home", {
"url": "/",
"template": require('./views/home.html'),
"controller": HomeCtrl,
'controllerAs': 'app'
})
}]);
export default MODULE_NAME;
The error message says:
Uncaught Error: [$injector:nomod] Module 'config' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
What did I missed here? Is there a better way to load an application wide config object to use in certain services?
Thanks for help!
When you create a module, you have to call module() with two args: the name and the dependencies. If you call it with only one parameter, you get the existing module with that name.
Change your module config declaration to:
export default angular.module('config', [])
.factory('config', () => {
return {
url: {
products: 'https://....'
},
products: []
}
}).name
In addition, I always export only the .name of a new module. When you import the module, you just need its name.
Hope it helps.
When you declare your dependent modules in your main module definition you need to use the string identifier, not the actual angular.module (at least here it's specified as Array[String]: https://docs.angularjs.org/api/ng/function/angular.module
So, you should change to this:
angular.module(MODULE_NAME, ['config'])
Please check Angular documentation - module dependencies it's array of strings, but not dependent modules types or instances
angular.module(name, [requires], [configFn]);
when
requires (optional) Array<string>
So your solution to use next declaration
angular.module(MODULE_NAME, ['config'])
https://docs.angularjs.org/api/ng/function/angular.module

Dynamic template in templatURL in angular2

I have been using ng-include in angular 1 whenever I had to include a tamplate dynamically in the page.
Now how to acheive this in angular 2. I have tried searching and found these :
https://groups.google.com/forum/#!topic/angular/ROkKDHboWoA ,
https://github.com/angular/angular/issues/2753
can someone explain how to do this in angular2 as the link says ng-include is not included due some security reasons.
Or atleast how to use a veriable in templateUrl property so that the veriable value can be handled on server side to serve the template...
And as you can see in this issue on the Angular repo, most probably we won't get that directive. There has been a long discussion whether we need this directive or not, and if not provided how we can implement it by our self.
I tried to make a simple example of how it can be implemented.
#Component({
selector: 'my-ng-include',
template: '<div #myNgIncludeContent></div>'
})
export class MyNgInclude implements OnInit {
#Input('src')
private templateUrl: string;
#ViewChild('myNgIncludeContent', { read: ViewContainerRef })
protected contentTarget: ViewContainerRef;
constructor(private componentResolver: ComponentResolver) {}
ngOnInit() {
var dynamicComponent = this.createContentComponent(this.templateUrl);
this.componentResolver.resolveComponent(dynamicComponent)
.then((factory: any) => this.contentTarget.createComponent(factory));
}
createContentComponent(templateUrl) {
#Component({
selector: 'my-ng-include-content',
templateUrl: templateUrl,
directives: FORM_DIRECTIVES,
})
class MyNgIncludeContent {}
return MyNgIncludeContent ;
}
}
For a demo check this Plunker.
Actually angular 2 has not featured this in the current build. Also as per the links added, I don't think this feature will be included.
A piece of javascript to dynamically add template using ajax call may be used.
Or possibly in future a dynamic template loader library will be available for use.
As of alpha.46 (and with ES6 JS):
In the parent file import file you wanted to include:
#Component({
selector: 'account'
})
#View({
templateUrl: './folder/containing/template.html'
})
Easy as that.
If you meant to import a component, this is what you do in the parent file:
import ComponentClassName from './folder/with/componentName';
...
#View({
directives: [ComponentClassName]
})
And inside the imported file of the child/component:
Define your ComponentClassName (you may add templateUrlto the #View just as demonstrated at the top).
Don't forget to export default ComponentClassName; at the bottom of the file.
There are not many examples in the official Angular 2 docs, but you stumble across it every once in a while.
As #binariedMe accurately describes, ng-include is off in Angular 2 due to security considerations. The recommended method is to use a custom directive with slightly more programmatical overhead.
Additionally, to prepare your Angular code for 2.0:
myApp.directive('myInclude', function() {
return {
templateUrl: 'mytemplate.html'
};
});
And rather than using ng-include on an element, simply add my-include:
<div my-include></div>
Following #binariedMe and this blog post http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2/, I was able to construct a solution that may work for you. Using an AJAX call and creating the custom component dynamically from the returned html content should fix this problem in creating a new my-ng-include custom directive.
import {
Component,
Directive,
ComponentFactory,
ComponentMetadata,
ComponentResolver,
Input,
ReflectiveInjector,
ViewContainerRef
} from '#angular/core';
import { Http } from '#angular/http';
export function createComponentFactory(resolver: ComponentResolver, metadata: ComponentMetadata): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);
return resolver.resolveComponent(decoratedCmp);
}
#Directive({
selector: 'my-ng-include'
})
export class MyNgInclude {
#Input() src: string;
constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver, private http: Http) {
}
ngOnChanges() {
if (!this.src) return;
this.http.get(this.src).toPromise().then((res) => {
const metadata = new ComponentMetadata({
selector: 'dynamic-html',
template: res.text(),
});
createComponentFactory(this.resolver, metadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, 0, injector, []);
});
});
}
}
Just simply use it as follows:
<my-ng-include [src]="someChangingProperty"></my-ng-include>

Ember DRY pattern for reusing "Ember.computed.alias"

I have a form that transitions through several views. Currently each controller.js file has a long list of these Ember.computed.alias. How can I break that out into one file and import it into each controller?
Currently in each controller.js
entityEmail: Ember.computed.alias('controllers.checkout.entityEmail'),
entityDOB: Ember.computed.alias('controllers.checkout.entityDOB'),
entityPhone: Ember.computed.alias('controllers.checkout.entityPhone'),
entityAddress1: Ember.computed.alias('controllers.checkout.entityAddress1'),
entityAddress2: Ember.computed.alias('controllers.checkout.entityAddress2'),
entityCity: Ember.computed.alias('controllers.checkout.entityCity'),
I would like to pull all that out into a file so I can simply import some 1 liner in each controller.js
This is a classic use-case for Ember.Mixin.
You can extract all these computed props into a single mixin and extend every controller (that needs to have these props) with it.
Add the following mixin to your app
// app/mixins/entity-form.js
import Ember from 'ember';
const { Mixin, inject, computed: { alias } } = Ember;
export default Mixin.create({
checkout: inject.controller(),
entityEmail: alias('checkout.entityEmail'),
entityDOB: alias('checkout.entityDOB'),
entityPhone: alias('checkout.entityPhone'),
entityAddress1: alias('checkout.entityAddress1'),
entityAddress2: alias('checkout.entityAddress2'),
entityCity: alias('checkout.entityCity')
});
And then use it in a controller
// app/controllers/example.js
import EntityFormMixin from 'yourAppName/mixins/entity-form';
const { Controller } = Ember;
export default Controller.extend(EntityFormMixin, {
// rest of controller's props and functions
});
Note: Ember.inject API is available since Ember 1.10.0. In case you are using an older version you need to replace the inject line with: needs: ['checkout'] and prefix the aliases with "controllers." like you did in your example.

Categories