I am trying to add angularjs component using the code as per below. app.component doesn't work like this
Where as if I execute app.component from outsite it works.
How to fix this issue. Notice that the API will return component names. I just need to render them.
app.service('applookup', function($http) {
this.register = function() {
return $http.get('http://localhost:3003/api/provider/fetch/app1').then(function(res){
app.component('appleComponent', {
template : 'test'
});
return res.data.componentList;
});
}
});
As #William and #Zooly mentioned in comments. We can add reference to $compileProvider.component as per below in app.config
app.register = {
component : function(name, object) {
$compileProvider.component(name, object);
return (this);
}
Then use app.register.component to register the component
I currently have a Hybrid Angular app (2.4.9 and 1.5.0) using angular-cli. Currently, when running our application, we are able to bootstrap the 1.5 app correctly:
// main.ts
import ...
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
angular.element(document).ready(() => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['myApp'], {strictDi: true});
});
});
However, in our test.ts file:
// test.ts
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import ...;
declare var __karma__: any;
declare var require: any;
__karma__.loaded = function () {};
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
// I'm assuming that I need to call 'boostrapModule()' somehow here...
platformBrowserDynamicTesting()
);
const context = require.context('./', true, /\.spec\.ts$/);
context.keys().map(context);
__karma__.start();
I'm not exactly sure how to bootstrap our 1.5 application into the test environment, all I've gotten is Module 'myApp' is not available!, and my Google skills have failed trying to find an example.
I was hoping the bounty I added last night would mean I could log on this morning to a nice solution laid out for me. Alas, it did not. So instead I spent the day cruising around many SO answers and github issues getting it to work. I'm sorry I did not keep track of everything that helped me to credit them, but here is my solution. It is probably not ideal, but it is working so far so I hope it is a good start.
This github issue indicates that downgradeComponent isn't going to work for now, so I went with what I assume is an older technique using UpgradeAdapter. Note that this technique does not use initTestEnvironment. Here are the relevant snippets, with some explanations below:
// downgrade.ts:
export const componentsToDowngrade = {
heroDetail: HeroDetailComponent,
...
};
export function downgradeForApp() {
forOwn(componentsToDowngrade, (component, name) => {
app.directive(name!, downgradeComponent({ component }));
});
}
// main.ts:
downgradeForApp();
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then((platformRef) => {
...
});
// test.ts:
require("../src/polyfills.ts");
require("zone.js/dist/proxy");
require('zone.js/dist/sync-test');
require("zone.js/dist/mocha-patch");
// test-helper.ts
let upgradeAdapterRef: UpgradeAdapterRef;
const upgradeAdapter = new UpgradeAdapter(AppModule);
forEach(componentsToDowngrade, (component, selectorName) => {
angular.module("app").directive(
selectorName!,
upgradeAdapter.downgradeNg2Component(component) as any,
);
});
export function useAdaptedModule() {
beforeEach(() => {
upgradeAdapterRef = upgradeAdapter.registerForNg1Tests(["app"]);
});
}
export function it(expectation: string, callback: () => void) {
test(expectation, (done) => {
inject(() => { }); // triggers some kind of needed initialization
upgradeAdapterRef.ready(() => {
try {
callback();
done();
} catch (ex) { done(ex); }
});
});
}
// hero-detail.component.spec.ts
import { it, useAdaptedModule } from "test-helpers/sd-app-helpers";
describe("", () => {
useAdaptedModule();
it("behaves as expected", () => { ... });
});
A few of the highlights from that code:
I downgrade components differently for tests than for the app, so I made a DRY list of them in downgrade.ts
I downgrade components for the main app from main.ts by calling downgradeForApp() as shown above (used with AOT for a production bundle), and also main-jit.ts, not shown above (used for development)
I showed the imports I needed to add to start integrating Angular components into my AngularJS tests. You may need more/different ones depending e.g. on whether your tests are asynchronous, or you use Jasmine instead of Mocha.
At the beginning of each test that needs to use downgraded components, I "bootstrap" things with useAdaptedModule() instead of beforeEach(angular.mock.module("app"));
I import an alternative it from my helpers, which wraps the it provided by Mocha. None of my tests are asynchronous; if you have some that are it may require tweaking. I do not know how it may need to be adapted for Jasmine.
A caveat: Instantiating the component must happen within an it callback so that it happens within upgradeAdapterRef.ready(...). Trying to do it within a beforeEach is too soon.
I want to upgrade a ng1 component to be used inside an ng2 component.
If I use just a template string the ng1 component, to be upgraded, it works. However, if I switch to using a templateUrl instead, the app crashes and give me this error:
angular.js:13920 Error: loading directive templates asynchronously is not supported
at RemoteUrlComponent.UpgradeComponent.compileTemplate (upgrade-static.umd.js:720)
at RemoteUrlComponent.UpgradeComponent (upgrade-static.umd.js:521)
at new RemoteUrlComponent (remote-url.component.ts:11)
at new Wrapper_RemoteUrlComponent (wrapper.ngfactory.js:7)
at View_AppComponent1.createInternal (component.ngfactory.js:73)
at View_AppComponent1.AppView.create (core.umd.js:12262)
at TemplateRef_.createEmbeddedView (core.umd.js:9320)
at ViewContainerRef_.createEmbeddedView (core.umd.js:9552)
at eval (common.umd.js:1670)
at DefaultIterableDiffer.forEachOperation (core.umd.js:4653)
Here is a plunk demonstrating my issue:
https://plnkr.co/edit/2fXvfc?p=info
I've followed the Angular 1 -> 2 upgrade guide and it seems that this code should work. I'm not quite sure why its not working.
After trying require with requireJS and the text plugin which did not work for me, I managed to make it work using 'ng-include' as follow:
angular.module('appName').component('nameComponent', {
template: `<ng-include src="'path_to_file/file-name.html'"></ng-include>`,
I hope this helps!
I found a quite cheap solution for the issue.
Just use template: require('./remote-url.component.html') instead of templateUrl: './remote-url.component.html' and it should work just fine!
This is really frustating because the Angular upgrade documentation specifically says it's ok to use templateUrl. Never mentions this async issue. I've found a way around it by using the $templateCache. I didn't want to change my angular 1 directive because it is used my angular 1 apps and will also be used by angular 4 apps. So I had to find a way to modify it on the fly. I used $delegate, $provider, and $templateCache. My code is below. I also use this to remove the replace attribute since it is deprecated.
function upgradeDirective(moduleName, invokedName) {
/** get the invoked directive */
angular.module(moduleName).config(config);
config.$inject = ['$provide'];
decorator.$inject = ['$delegate', '$templateCache'];
function config($provide) {
$provide.decorator(invokedName + 'Directive', decorator);
}
function decorator($delegate, $templateCache) {
/** get the directive reference */
var directive = $delegate[0];
/** remove deprecated attributes */
if (directive.hasOwnProperty('replace')){
delete directive.replace;
}
/** check for templateUrl and get template from cache */
if (directive.hasOwnProperty('templateUrl')){
/** get the template key */
var key = directive.templateUrl.substring(directive.templateUrl.indexOf('app/'));
/** remove templateUrl */
delete directive.templateUrl;
/** add template and get from cache */
directive.template = $templateCache.get(key);
}
/** return the delegate */
return $delegate;
}
}
upgradeDirective('moduleName', 'moduleDirectiveName');
Most of the answers given here involve pre-loading the template in some way so as to make it available synchronously to the directive.
If you want to avoid doing this - e.g. if you have a large AngularJS application that contains many templates, and you don't want to download them all up front - you can simply wrap your directive in a synchronously loaded version instead.
E.g., if you have a directive called myDirective, which has an asynchronously loaded templateUrl which you don't want to download up front, you can do this instead:
angular
.module('my-module')
.directive('myDirectiveWrapper', function() {
return {
restrict: 'E',
template: "<my-directive></my-directive>",
}
});
Then your Upgraded Angular directive just needs to supply 'myDirectiveWrapper' instead of 'myDirective' in it's super() call to the extended UpgradeComponent.
A pretty low-tech solution to this issue is to load your templates up in your index.html, and assign them IDs that match the templateUrls the directives are looking for, ie:
<script type="text/ng-template" id="some/file/path.html">
<div>
<p>Here's my template!</p>
</div>
</script>
Angular then automatically puts the template into the $templateCache, which is where UpgradeComponent's compileTemplate is looking for the template to begin with, so without changing the templateUrl in your directive, things will work because the id matches the templateUrl.
If you check the source code of UpgradeComponent (see below), you can see commented out code that deals with fetching the url, so it must be in the works, but for the time being this could be a viable solution and even a scriptable one.
private compileTemplate(directive: angular.IDirective): angular.ILinkFn {
if (this.directive.template !== undefined) {
return this.compileHtml(getOrCall(this.directive.template));
} else if (this.directive.templateUrl) {
const url = getOrCall(this.directive.templateUrl);
const html = this.$templateCache.get(url) as string;
if (html !== undefined) {
return this.compileHtml(html);
} else {
throw new Error('loading directive templates asynchronously is not supported');
// return new Promise((resolve, reject) => {
// this.$httpBackend('GET', url, null, (status: number, response: string) => {
// if (status == 200) {
// resolve(this.compileHtml(this.$templateCache.put(url, response)));
// } else {
// reject(`GET component template from '${url}' returned '${status}: ${response}'`);
// }
// });
// });
}
} else {
throw new Error(`Directive '${this.name}' is not a component, it is missing template.`);
}
}
If you don't want to modify your Webpack configuration, the quick/dirty solution is to use the raw-loader import syntax:
template: require('!raw-loader!./your-template.html')
As a workaround I used $templateCache and $templateRequest to put templates in $templateCache for Angular needed templates, on AngularJS run as follows:
app.run(['$templateCache', '$templateRequest', function($templateCache, $templateRequest) {
var templateUrlList = [
'app/modules/common/header.html',
...
];
templateUrlList.forEach(function (templateUrl) {
if ($templateCache.get(templateUrl) === undefined) {
$templateRequest(templateUrl)
.then(function (templateContent) {
$templateCache.put(templateUrl, templateContent);
});
}
});
}]);
I've created a method utility to solve this issue.
Basically it adds the template url content to angular's templateCache,
using requireJS and "text.js":
initTemplateUrls(templateUrlList) {
app.run(function ($templateCache) {
templateUrlList.forEach(templateUrl => {
if ($templateCache.get(templateUrl) === undefined) {
$templateCache.put(templateUrl, 'temporaryValue');
require(['text!' + templateUrl],
function (templateContent) {
$templateCache.put(templateUrl, templateContent);
}
);
}
});
});
What you should do is put this method utility in appmodule.ts for example, and then create a list of templateUrls that you are about to upgrade from your angular directive, for example:
const templateUrlList = [
'/app/#fingerprint#/common/directives/grid/pGrid.html',
];
I use webpack's require.context for this:
templates-factory.js
import {resolve} from 'path';
/**
* Wrap given context in AngularJS $templateCache
* #param ctx - A context module
* #param dir - module directory
* #returns {function(...*): void} - AngularJS Run function
*/
export const templatesFactory = (ctx, dir, filename) => {
return $templateCache => ctx.keys().forEach(key => {
const templateId = (() => {
switch (typeof filename) {
case 'function':
return resolve(dir, filename(key));
case 'string':
return resolve(dir, filename);
default:
return resolve(dir, key);
}
})();
$templateCache.put(templateId, ctx(key));
});
};
app.html-bundle.js
import {templatesFactory} from './templates-factory';
const ctx = require.context('./', true, /\.html$/);
export const AppHtmlBundle = angular.module('AppHtmlBundle', [])
.run(templatesFactory(ctx, __dirname))
.name;
Don't forget to add html-loader to your webpack.config.js:
[{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
minimize: false,
root: path.resolve(__dirname, './src')
}
}
}]
Also you may need to convert relative paths to absolute one. I use my self-written babel plugin ng-template-url-absolutify for this purpose:
[{
test: /\.(es6|js)$/,
include: [path.resolve(__dirname, 'src')],
exclude: /node_modules/,
loader: 'babel-loader',
options: {
plugins: [
'#babel/plugin-syntax-dynamic-import',
['ng-template-url-absolutify', {baseDir: path.resolve(__dirname, 'src'), baseUrl: ''}]
],
presets: [['#babel/preset-env', {'modules': false}]]
}
},
While trying to setup a test environment I ran into the following problem. When I run the tests (using mocha ./src/test/.setup.js ./src/test/**.test.js), I get a Element is not defined error:
app\node_modules\onsenui\js\onsenui.js:603
var originalCreateShadowRoot = Element.prototype.createShadowRoot;
^
ReferenceError: Element is not defined
at app\node_modules\onsenui\js\onsenui.js:603:34
at app\node_modules\onsenui\js\onsenui.js:359:7
at Array.forEach (native)
at initializeModules (app\node_modules\onsenui\js\onsenui.js:358:13)
at app\node_modules\onsenui\js\onsenui.js:908:5
(...)
How is this possible, isn't Element a basic DOM element?
The following versions are used:
enzyme#2.4.1
mocha#3.0.2
onsenui#2.0.0-rc.17
react#15.3.1
react-onsenui#0.7.5
The following .setup.js file is used:
require('babel-register')({
presets: ["react","airbnb","es2015"]
});
var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
documentRef = document;
And the test file is as follows:
import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import * as Ons from 'react-onsenui';
import MainPage from '../react/MainPage/MainPage.jsx';
describe("Component MainPage", function() {
it("should have a Ons.Page component", function() {
const wrapper = mount(<MainPage />);
expect(wrapper.find(Ons.Page)).to.equal(true);
});
});
onsenui seems to be written for a browser enviroment, whilst you're executing it in a nodejs enviroment.
Element is a DOM api. nodejs has no DOM.
You could research using jsDom which is a node module that mimics the browser DOM for nodejs.
Edit:
I think this package could solve your problems: https://github.com/rstacruz/jsdom-global I checked, it should place a 'Element' property in the global.
I am trying to set up a marionette project using browserify and es6. When creating a CollectionView I am getting the error Uncaught TypeError: Backbone.ChildViewContainer is not a constructor.
Am I missing loading something? Can't seem to find anything about this on the internet.
Here is my collection view:
import {ItemView, CollectionView} from 'backbone.marionette';
import navTemplate from '../templates/navigation.hbs';
import navItemTemplate from '../templates/_navItem.hbs';
var NavigationItem = ItemView.extend({
template: navItemTemplate
});
var NavigationView = CollectionView.extend({
template: navTemplate,
childView: NavigationItem,
childViewContainer: '.left-navigation',
});
export default NavigationView;
and my layout that is creating it
import {LayoutView} from 'backbone.marionette';
import layoutTemplate from './templates/layout.hbs';
import NavigationView from './Views/navigation';
export default class AppLayout extends LayoutView {
constructor(options) {
super(options);
this.template = layoutTemplate;
}
regions() {
return {
'navigation': '.left-aside'
};
}
onRender() {
console.log(this.getRegion('navigation'));
this.getRegion('navigation').show(new NavigationView());
}
}
I am also using a shim to use backbone.radio but that shouldn't impact this:
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['backbone.marionette', 'backbone.radio', 'underscore'], factory);
} else if (typeof exports !== 'undefined') {
module.exports = factory(require('backbone.marionette'), require('backbone.radio'), require('underscore'));
} else {
factory(root.Backbone.Marionette, root.Backbone.Radio, root._);
}
}(this, function(Marionette, Radio, _) {
'use strict';
Marionette.Application.prototype._initChannel = function () {
this.channelName = _.result(this, 'channelName') || 'global';
this.channel = _.result(this, 'channel') || Radio.channel(this.channelName);
};
}));
Edit: I have found that my compiled file has many copies of backbone so that might be the problem...
I came across this problem with Webpack. I was able to work around it by specifying an alias in webpack.config.js
resolve: {
extensions: ['', '.js', '.ts'],
alias: {
'backbone': 'backbone.marionette/node_modules/backbone'
}
},
i.e. making sure all references to backbone use the one installed as a dependency of Marionette.
I've never used Browserify, but perhaps you can do something similar by installing aliasify and adding this to your package.json:
{
"aliasify": {
"aliases": {
"backbone": "backbone.marionette/node_modules/backbone"
}
}
Was getting this same error this morning when using an older JSPM config file pointing to Marionette 2.4.1 and Backbone 1.2.1, and Google linked me here... Updating to the more recent Marionette 2.4.5 and Backbone 1.3.2 releases seemed to fix things for me. What versions are you running?