Angular2 - Dynamically load component from module - dynamic

in my angular app I have the following:
export class MyComponent {
subcompPath = "path-to-subcomp#SubcompClassName";
#ViewChild("placeholder", { read: ViewComponentRef }) placeholderRef: ViewComponentRef;
/* Constructor where Compiler, ComponentFactoryResolver are injected */
loadSubcomponent() {
let [path, componentName] = this.subcompPath.split("#");
(<any>window).System.import(path)
.then((module: any) => module[componentName])
.then((type: any) => {
return this._compiler.compileComponentAsync(type)
})
.then((factory: any) => {
let componentRef = this.placeholderRef.createComponent(factory, 0);
});
}
}
My sub-component declares providers and stuff, directives and pipes.
And now RC6 is out to break everything yet again. Components can't declare directives and pipes, but they must be in the module where the component is declared.
So I have to load with SystemJS not the component itself but the module.
Ok, and then I should use
return this._compiler.compileModuleAndAllComponentsAsync(type)
Fine, but how do I get a reference to the factory of this specific component? That factory is all I need, the placeholderRef wants it in its createComponent method, right?
I tried to dig into the angular2 source code from github but it's quite vast, I should try from VS Code or something, with intellisense, but I'm lazy...and I should read this stuff from documentation, which is quite lackluster in angular.io for this particular argument, which is lazy loading of components and modules WITHOUT the router.
Any help is appreciated, I think the solution is simple to apply but hard to find without official documentation.

Update:
If you want to use it together with aot compilation you should manually provide Compiler like
export function createJitCompiler () {
return new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();
}
...
providers: [
{ provide: Compiler, useFactory: createJitCompiler}
],
Example
Old version
It might help you:
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
.then(({moduleFactory, componentFactories}) => {
const compFactory = componentFactories
.find(x => x.componentType === DynamicComponent);
const cmpRef = this.placeholderRef.createComponent(compFactory, 0);
See also
related answer with example
How to use variable to define templateUrl in Angular2
sample code from angular2 source code

Based on yurzui's answer I've come to the following code:
export class MyComponent {
subcompPath = "path-to-subcompMODULE#SubcompClassName";
#ViewChild("placeholder", { read: ViewComponentRef }) placeholderRef: ViewComponentRef;
/* Constructor where Compiler, ComponentFactoryResolver are injected */
loadSubcomponent() {
let [modulePath, componentName] = this.subcompPath.split("#");
(<any>window).System.import(modulePath)
.then((module: any) => module["default"]) // Or pass the module class name too
.then((type: any) => {
return this._compiler.compileModuleAndAllComponentsAsync(type)
})
.then((moduleWithFactories: ModuleWithComponentFactories<any>) => {
const factory = moduleWithFactories.componentFactories.find(x => x.componentType.name === componentName); // Crucial: componentType.name, not componentType!!
let componentRef = this.placeholderRef.createComponent(factory, 0);
});
}
}

Related

Where to setup an API in Vue.js

I need to use an API that requires initialization with an API key and some other details within my Vue.js app.
var client = api_name('app_id', 'api_key', ...)
I would need to make several API calls with the client object in multiple components in my app
client.api_function(...)
How can I avoid repeating the initialization step in every component?
I'm thinking about using a global mixin in main.js for that
Vue.mixin({
data: function() {
return {
get client() {
return api_name('app_id', 'api_key');
}
}
}
})
Is this a good approach?
I'd rather move your getter to a service and just import, where you actually need it. It doesn't seem to fit into data section, more like methods. A mixin is a decent approach if you need lots of similar stuff: variables, methods, hooks etc. Creating a mixin for only 1 method looks like overkill to me.
// helper.js
export function getClient () {
// do stuff
}
// MyComponent.vue
import { getClient } from 'helpers/helper`
// Vue instance
methods: {
getClient
}
How about creating a helper file and writing a plugin that exposes your api url's? You can then create prototypes on the vue instance. Here's an example,
const helper = install(Vue){
const VueInstance = vue
VueInstance.prototype.$login = `${baseURL}/login`
}
export default helper
This way you can access url's globally using this.$login. Please note $ is a convention to avoid naming conflicts and easy to remember that it is a plugin.

IntelliJ IDEA reporting unresolved variable TypeScript errors, but won't allow ':any'

I have the following bit of TypeScript code in an Angular 2 application.
constructor(private windows:WindowService, private http:Http) {
http.get('config.json')
.map(res => res.json())
.subscribe(config => {
this.oAuthCallbackUrl = config.callbackUrl; // here
this.oAuthTokenUrl = config.implicitGrantUrl; // here
this.oAuthTokenUrl = this.oAuthTokenUrl
.replace('__callbackUrl__', config.callbackUrl) // here
.replace('__clientId__', config.clientId) // here
.replace('__scopes__', config.scopes); // here
this.oAuthUserUrl = config.userInfoUrl; // here
this.oAuthUserNameField = config.userInfoNameField; // here
})
}
Every place where I have a // here I get an error from the IDE saying that I have an 'unresolved variable', such as unresolved variable callbackUrl.
The issue is that this config object is the result of a JSON file being fetched by the app, and I have no idea how I would define its type ahead of time.
I thought I might be able to change the subscribe line to show .subscribe(config:any => { or something, but that has not worked.
The code compiles fine. Everything works in the browser (and through Webpack) without issue. I just would like to get rid of the error in the IDE without having to add 7 //noinspection TypeScriptUnresolvedVariable comments to surpress them.
As #DCoder mentioned in a comment above, the solution was to wrap the config parameter in parenthesis and then add the type to it.
constructor(private windows:WindowService, private http:Http) {
http.get('config.json')
.map(res => res.json())
.subscribe((config:any) => { // <-- WORKS NOW!
this.oAuthCallbackUrl = config.callbackUrl;
this.oAuthTokenUrl = config.implicitGrantUrl;
this.oAuthTokenUrl = this.oAuthTokenUrl
.replace('__callbackUrl__', config.callbackUrl)
.replace('__clientId__', config.clientId)
.replace('__scopes__', config.scopes);
this.oAuthUserUrl = config.userInfoUrl;
this.oAuthUserNameField = config.userInfoNameField;
})
}
It was the lack of parenthesis that was keeping me from being able to add typing information of any kind on config.

Aurelia: how to manage sessions

I'm trying to develop a website where the nav-bar items depend on the role of the user who is logged in.
As Patrick Walter suggested on his blog, I was thinking to create a session.js file where I would store information about the current user: their username and role. I would then inject this file in nav-bar.js and create a filter for the routes, for which the user does not have access to. Everything worked fine until I hit the refresh button... In fact, it creates a new session object and I loose all the information store in the previous one.
I have seen in the docs the singleton method, but I'm not sure how to use it. If I insert it in my code such as below, I get the message: aurelia.use.singleton is not a function.
import config from './auth-config';
export function configure(aurelia) {
console.log('Hello from animation-main config');
aurelia.use
.singleton(Session)
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-animator-css')
.plugin('paulvanbladel/aurelia-auth', (baseConfig) => {
baseConfig.configure(config);
});
aurelia.start().then(a => a.setRoot());
}
export class Session {
username = '';
role = '';
reset() {
console.log('Resetting session');
this.username = '';
this.role = '';
};
}
My last idea would be to encrypt the role/username and use the browser's session to store the information. But I wanted to ask to more experienced developers their opinion about the topic.
Thanks for your help!
EDIT: Here is my code for session.js
export class Session {
username = '';
role = '';
reset() {
console.log('Resetting session');
this.username = '';
this.role = '';
};
}
And this is how I inject it:
import {Session} from './services/session';
#inject(Session)
export class RoleFilterValueConverter {
constructor(session) {
console.log('Hello from RoleFilter constructor', session)
this.session = session;
};
toView(routes, role) {
console.log('Hello from view', role, this.session)
if (this.session.role == 'Superuser')
return routes;
return routes.filter(r => {
var res = !r.config.role || (r.config.role == this.session.role);
return res
});
}
}
In the main entry point (let's assume it's index.html) you should have something like this:
<body aurelia-app="path/to/main">
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
System.import('aurelia-bootstrapper');
</script>
</body>
This imports the entire aurelia enviorment and so when it reaches the export function configure(aurelia) { ... } it should pass an instance of type Aurelia and bind it to the aurelia parameter and should resolve your aurelia.use.singleton is not a function. error. After that, in your session.js file when using #inject(Session) it should pass the same instance you declared at startup.
I also implemented a singleton session object to store user data and have chosen this method because it's more convenient to rely on dependency injection rather than always calling a method to get user data from a cookie.
Though Laurentiu's answer is not bad, there are better ways to handle this that do not add complexity to your app.
You do not need to need to specify this as a singleton. This particular function is more for an edge case where you would want to expose a particular item to the dependency injection container as a singleton before startup.
In fact, the Aurelia dependency injection framework treats all modules as singletons unless specified otherwise. Thus, the code should work as you have it written there, without the configure function.
I've written up an in-depth blog that you maay find helpful here: http://davismj.me/blog/aurelia-auth-pt2/

Prevent duplicate routes in express.js

Is there a nice way to prevent duplicate routes from being registered in express? I have a pretty large application with hundreds of routes across different files, and it gets difficult to know if I've already registered a certain route when I go to add a new one. For example, I'd like to throw an error when express gets to routes487.js:
File: routes1.js
var ctrl = require('../controllers/testctrl');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi);
app.get('/there', auth.getToken, ctrl.there);
};
File: routes487.js
var ctrl = require('../controllers/testctrl487');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi487);
};
You could try a custom solution by wrapping express methods with the validation. Consider the following modification to your express app:
// route-validation.js
module.exports = function (app) {
var existingRoutes = {}
, originalMethods = [];
// Returns true if the route is already registered.
function routeExists(verb, path) {
return existingRoutes[verb] &&
existingRoutes[verb].indexOf(path) > -1;
}
function registerRoute(verb, path) {
if (!existingRoutes[verb]) existingRoutes[verb] = [];
existingRoutes[verb].push(path);
}
// Return a new app method that will check repeated routes.
function validatedMethod(verb) {
return function() {
// If the route exists, app.VERB will throw.
if (routeExists(verb, arguments[0]) {
throw new Error("Can't register duplicate handler for path", arguments[0]);
}
// Otherwise, the route is saved and the original express method is called.
registerRoute(verb, arguments[0]);
originalMethods[verb].apply(app, arguments);
}
}
['get', 'post', 'put', 'delete', 'all'].forEach(function (verb) {
// Save original methods for internal use.
originalMethods[verb] = app[verb];
// Replace by our own route-validator methods.
app[verb] = validatedMethod(verb);
});
};
You just need to pass your app to this function after creation and duplicate route checking will be implemented. Note that you might need other "verbs" (OPTIONS, HEAD).
If you don't want to mess with express' methods (we don't know whether or how express itself or middleware modules will use them), you can use an intermediate layer (i.e., you actually wrap your app object instead of modifying its methods). I actually feel that would be a better solution, but I feel lazy to type it right now :)

Define containing a deferred object

About require instruction, Dojo official doc says:
if a configuration object is provided, it is passed to the configuration API as described >in Configuration. Next, the dependencies listed in dependencies (if any) are resolved. >Finally, callback (if any) is executed with the resolved dependencies passed in as >arguments
With my example (below), I want to use users.json data in main program, but callback of the called module (monModule.js) is not executed and I obtain an empty object.
How can I proceed ?
Thanks to you.
1 )Main program (extract)
<script type="text/javascript">
require(["monModule"],function(monModule){
console.log(monModule);// returns {}
});
</script>
2) Called module (monModule.js) :
define(["dojo/request/xhr","dojo/json"],function(xhr,json){
xhr("users.json",{handleAs:"JSON"}).
then(function(data){console.log(return data;});
});
3) users.json
[
{‘ id’:"id1",’nom’:"nom1"},
{ ‘id’:"id2",’nom’:"nom2"},
{‘id’:"id3",’nom’:"nom3"}
]
the call in the then is also async so that is why you are getting back an empty object. you will have to add another deferred to your module for it to run and a then in your code
define( ["dojo/request/xhr","dojo/json" , "dojo/Deferred"], function( xhr, json , deferred) {
var def = new deferred();
xhr("users.json", { handleAs :"JSON" }).then(
function( data ) {
return def.resolve(data);
}
);
return def.promise;;
});
And in Code get it by
require(["custom/monModule"],function(monModule){
monModule.then(function(users){
console.log(users);
});
});