So, I'm trying to setup Aurelia in my Angular 1 web application so I can slowly upgrade. I need to do that since the application is too big and migrating everything at once would be impossible.
So, in my Aurelia folder I created a component folder with two components (aurelia-component.js and another-component.js with their views aurelia-component.html and another-component.html), I won't put the javascript as they are just two classes with one property, the html for both is the same, the only thing that changes is the text property value so I can differentiate them:
<template>
<div>${text}</div>
</template>
My entry point main.js looks like this:
export function configure(aurelia) {
aurelia.use
.basicConfiguration()
.developmentLogging()
.globalResources('components/aurelia-component')
.globalResources('components/another-component');
//window.aurelia = aurelia;
aurelia.start()
.then(a => {
window.aurelia = a;
});
}
As you can see, this puts Aurelia in the window object so I can access it from my Angular app, I'll improve this later.
In my angular app I have this directive:
'use strict';
function AureliaContainer() {
function Link($scope, element, attrs) {
window.aurelia.enhance(element[0]);
}
//
return {
restrict: 'A',
link: Link
};
}
module.exports = AureliaContainer;
I set this up in my app root with:
app.directive('aureliaContainer', require('./directives/aurelia.container'));
And in my Angular View I have these divs with my directive that calls the enhance function from Aurelia:
<div aurelia-container>
<aurelia-component></aurelia-component>
</div>
<div aurelia-container>
<another-component></another-component>
</div>
The reason I have two aurelia-container in the html is that I know I'll have to have more than one when I'm migrating the application.
And this works fine, both components load normally in the screen.
The problem is when I try to call another component from within one of those components.
What I did was, I created a new component called test-component.js with its view test-component.html. The html for this is just:
<template>
<h1>Header</h1>
</template>
And then, from the aurelia-component.html I called it using:
<template>
<require from="./test-component"></require>
<div>${text}</div>
<test-component></test-component>
</template>
Now, when I load the page, the test-component actually loads but the <div>${text}</div part of aurelia-component doesn't and I get this error in the console:
Uncaught (in promise) TypeError: Cannot read property 'behaviorInstructions' of undefined
I really don't understand why this error is happening, I should be able to load a custom element from within another one normally, shouldn't I. Or is there a limitation when you use enhance?
I also tried to use setRoot in both divs with no success, just one of them is loaded.
Maybe there's a better approach for this?
Again, I can't migrate my entire application at once, it's just no feasible.
Thanks in advance for the help.
First off, I know nothing about progressive enhancement in Aurelia. And cannot comment about its suitability for your scenario.
But I am wondering if maybe you missed some Au dependencies (like binding or templating?)
http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/app-configuration-and-startup/8
aurelia.use
.defaultBindingLanguage()
.defaultResources()
.developmentLogging()
.globalResources('resources/my-component');
That might explain why it fails when you want it to render a template?
Related
TypeError: cannot set properties of null (setting 'innerHtml')
I have created a simple angular service that initializes the inner html of a div tag in the main component of my angular project and is called in multiple components. When I run my tests I get the above karma error. I assume this is because the component is not created in the service.spec.ts file. I have checked and the class is defined in the main html file.
service.ts function:
onCLick(value: string): void {
document.querySelector('div.class').innerHtml = value;
}
service.spec.ts:
descirbe('ClickedService', () => {
let service: ClickedService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ClickedService);
});
to("#onClick should add to innerHtml", () => {
service.onClick('test value'); // error is here
});
});
welcome to the StackOverflow. To be honest, I wouldn't bother with fixing this unit test, because it looks like your approach to update the value is completely incorrect in the first place.
In Angular world, Service should provide you with values, and it can obtain them either from server via HTTP, or as a result of internal calculation. Then it's up to a Component, which is using this service, to deal with the value and display it if needed.
The reason why your test is not working is, that while creating the TestBed for the service, HTML is not expected and you are not providing any. Your querySelector within the service can't find the div you are looking for and you can't set innerHtml value to null.
If you really want to make this value and this logic available within the application, move it to a standalone component. You can then add it wherever you want, it will wrap the logic and it will prevent the repetition of the code. Don't use service with JS queries to update HTML, this will only lead to serious issues in the future.
Check this article about binding in angular to get better idea of what to do.
In the project I'm working on there some components structured like this:
;(function () {
'use strict'
angular
.module('a')
.component('comp', {
templateUrl: function (Config) {
return Config.path + '/a/a.template.html'
}
...
And every component had its own template file, but there were little to no difference beetween them so I created one template file to be used in all of them, but when I changed a.template.html to b.template.html only in some of the components the changes took place, and when I inspected the unchanged elements the HTML that was being used was from a.template.html. Why only some of the components being changed? (Also the components have the same hiarchy and are also really alike)
I figured it out, turns out that my browser was storing the HTML in the cache, so what worked was to just clear the browser cache.
I am trying to do a PoC for ngUpgrade on one module of my app, and I'm running into an issue with transclusion/content projection along with AngularJS requires.
Let's say there's a downgraded Angular component defined like:
#Component({
selector: 'common-filter',
template: `
<ajs-filter>
<div>Common Filter</div>
<ajs-button text="Outside of content projection"></ajs-button>
<ng-content></ng-content>
</ajs-filter>
})
export class CommonFilter {}
ajsButton is an upgraded AngularJS component that requires a parent controller:
require: {
ajsFilterCtrl: '^ajsFilter'
}
A normal use of common-filter works perfectly fine, however when projecting an ajs-button like:
<common-filter>
<ajs-button text="Inside of content projection"></ajs-button>
</common-filter>
This error is thrown:
Unhandled Promise rejection: [$compile:ctreq] Controller 'ajsFilter', required by directive 'ajsButton', can't be found!
Is there any way around this? I know that I can rewrite the surrounding classes, but many other apps use them and I need to be able to upgrade the apps gradually.
Working Example:
https://stackblitz.com/edit/ngupgradestatic-playground-uvr14t
Snippets above are from index.html and common-filter.component.ts. This is a (more-or-less) minimal example based on much more complicated components. I'm going with the downgradeModule approach for performance reasons.
A maintainer, gkalpak, provided a workaround for this issue in the Github issue that I posted for this:
https://github.com/angular/angular/issues/24846#issuecomment-404448494
In short, this is an issue with the order of initialization of the different controllers.
To get around it, you can make the parent require optional and then use setTimeout to wait until the parent controller is initialized:
this.$onInit = () => {
this.requireAjsFilterCtrl().then(() => console.log('!', !!this.ajsFilterCtrl));
}
this.requireAjsFilterCtrl = () => {
return new Promise(resolve => {
setTimeout(() => {
this.ajsFilterCtrl = this.ajsFilterCtrl || $element.controller('ajsFilter');
resolve(this.ajsFilterCtrl);
});
});
};
The workaround can be seen in action here:
https://stackblitz.com/edit/ngupgradestatic-playground-a6zkwb
If I have a template in a component that references non-existant components, angular 1.6 seems perfectly happy to render it as nothing at all. For example I have a route currently that looks like:
when('/something',{
title: 'Something',
template: "<mycomponent></mycomponent>",
})
If I forget to register mycomponent on my application, this route renders nothing. Is there some mode I can use in angular that will cause a harder error in a case like that? Or at least print something to the console when it occurs?
To be perfectly clear, I have this issue with both top level components referenced by the router, as well as other child components that those reference recursively.
No, there is no option for that. By the way "non rendered" components are a benefit IMO, because you could override this slot later.
A short example:
when('/something',{
title: 'Something',
template: "<slot><mycomponent></mycomponent></slot>",
})
assume you want to override the ui-part of mycomponent, just define a component for "slot"
There was a routeProvider.otherwise before. Not sure if it’s still supported. I’m on a phone so limited. Let me know how it goes.
UI Router supports components for routes, this means that it is capable of triggering an error if a component doesn't exist.
AngularJS currently doesn't offer component router. This means that route template is compiled as any other template with $compile, and unknown selectors are just ignored.
It is possible to patch ngRoute ngView directive to additionally check if a route that is expected to route to a component actually has it compiled.
// the whole list can be retrieved from commonly available sources
var htmlTags = ['p', 'div', ...];
app.directive('ngView', function () {
return function($scope, $element, $attrs) {
if (!DEBUG_MODE)
return;
if ($element.children().length !== 1)
return;
var routeComponent = angular.element($element.children()[0]);
var routeComponentTagName = routeComponent.prop('tagName').toLowerCase();
if (htmlTags.indexOf(routeComponentTagName) >= 0)
return;
var routeComponentName = $attrs.$normalize(routeComponentTagName);
if (!routeComponent.controller(routeComponentName)) {
console.warn(routeComponentTagName + ' element is non-standard yet not a component');
}
}
});
AngularJS already has $normalize() which is primarily intended for attributes and strips some specific prefixes, but as long as the components names don't have x or data prefix, it can work as generic kebab to camel case transformer.
There may be other ways to detect if a component was compiled on child element. The code above will trigger false negative if there already is parent routeComponentName component, because controller() travels up the hierarchy.
And the proper way to handle this problem is to provide test coverage for the application. Router-related cases should likely be handled in integration or e2e tests.
I'm trying to utilize two 3rd party widgets on a website however cannot quite figure out how to get Ember.js to cooperate. I found lots on Views and have found that they're deprecated now and Components seem the way to go however I'm not sure how to make this work...
I have various city-based templates that require:
<script type="text/javascript" src="http://www.topix.net/iframe/city/atco-nj?js=1"></script>
and one other that looks like this:
<script>document.write('<script src="... + Math.random() + ..."></script>');</script>
How would I do this with Components or a better alternative!?
For this you don't really need a component, you could just create a template and inject it wherever you need it. However I'm not 100% what are city based templates but just to output html you can just use a template template / helper:
using a template (known as partial):
run (if using ember cli , if not just create the template file somewhere, again assuming you have some way you're compiling templates on the server)..
ember g partial inject_city
then:
//inject_city.hbs
<script type="text/javascript" src="http://www.topix.net/iframe/city/atco-nj?js=1"></script>
then in your main template:
{{partial 'inject_city'}}
Further reading: http://guides.emberjs.com/v1.10.0/templates/writing-helpers/
using a helper (notice to return html you must use the safestring)
Ember.Handlebars.helper('injectScript', function(value, options) {
return new Ember.Handlebars.SafeString("<script>document.write('<script src="... + Math.random() + ..."></script>');</script> );
});
In version 1.13.0 and above the syntax is different:
import Ember from "ember";
export default Ember.Helper.helper(function(params) {
return Ember.String.htmlSafe(`<b>${params[0]}</b>`);
});
(Notice you should generate a helper, wrap it with Helper.helper and return Ember.String.htmlSafe).
further reading: http://guides.emberjs.com/v1.10.0/templates/writing-helpers/
However the best way is to include libraries in your ember build / build your own component from by using the building blocks, and not just include a whole script..The ember documentation explains about components pretty well and ember-cli docs explain how to include third party libs..
Best of luck!
I got this to work by making a component. I had the same sort of problem, I wanted to draw some pie charts at load time of the page using charts.js
SO i defined the charts and ran the js to create them in a component.
heres the component 'js-charts':
export default Ember.Component.extend({
didInsertElement: function() { insert script and or methods to run }
});
This will always trigger because of the didInsertElement.
and in the template of the page your rendering just add {{js-charts}} component