Rendering part of component outside of the component itself - javascript

I have JSFiddle like this:
https://jsfiddle.net/ownvjjow/
Basically the problem is that I'd like to render some part of component x outside of the element(x) itself. Like in the outer scope, but with preserving the scope of it in other component(y). I have part of the component that may be wanted to be rendered with targetElement set, but it seems like ng-repeat have some problem with it. The fiddle returns some other error, the one I get in my app is:
I could propably break the part that I want to be "moving" as another component then conditionally render it in other place providing the bindings: {... , controller: '<'} then expose the scope from component x to the x.y but I was wondering if there is an option to compile this part of component x and inject it in other place with everything working correctly.
I am not sure if my approach is right or maybe I should think about something different, if you know any other solution/idea then I would be grateful if you let me know about it.

fiddle
if(this.options.targetElement) {
var parentElem = angular.element((this.options.targetElement));
var childElem = $compile(this.container)($scope)[0];
$timeout( function(){
parentElem.append(childElem);
}, 0, false );
}
This will give AngularJS time to finish its compile.
Hope this help.

Related

Can I force angular to give error if a component doesn't exist?

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.

Is it possible to change where AngularJS looks for variables?

Now, I know this is an off-the-wall question and that there is probably not going to be any API level access for doing this. I also understand that doing this is completely unnecessary. However, the implementation that I am aiming for requires for me to be able to make {{ variable }} look inside of the $scope.object instead of the $scope itself.
For example:
Controller($scope) {
$scope.active = ...;
}
In this case you can get the active object through {{active}} and any child elements through {{active.broken}} however for the sake of humoring me, lets assume that all of the variables I'm ever going to have to obtain is going to be part of that active object. So I'll be typing things like.. (Data not related)
{{active.title}}
{{active.author}}
{{active.content}}
You could just say "Well why not just move the title/author/content into the $scope and keep it outside of the active object, as that would achieve the desired result of this:
{{title}}
{{author}}
{{content}}
Well, that's where the problem comes in. This is a controller that is not exposed to my end-user, however the end-user does have a completely mutable object (in this example: active) that they can modify. This object has many [optional] listeners and callbacks that are invoked by the application controller when necessary.
The user's that have used my application during testing have commented on what a drag it is to have to type in active. before everything in order to get the data they wanted. Considering data from the $scope is never rendered to the screen, and only data from active is, I was wondering if perhaps there was a way to change where AngularJS looks when parsing/binding data.
If the goal is to evaluate expressions in a different context than $scope, that can be done with the $parse service.
app.controller("myVm", function($scope, $parse) {
var vm = $scope;
vm.active = { a: 5,
b: 3
};
var.myFn = $parse("a+b");
vm.total = myFn(vm.active);
console.log(vm.total);
});
The above example shows how to evaluate the expression a+b using the properties of the $scope.active object.
The DEMO on JSFiddle

Angular Component: how to specified value for output binding function defined in parent?

I wanted to use Angular 1.5's component to get benefit from its one-way binding: <hero-detail hero="ctrl.hero" save="ctrl.save(hero)"></hero-detail>. But, as Todd Motto points on his blog: Todd Motto's blog: One-way data-binding in Angular 1.5, it works properly only for primitives. So I had to bind primitives:
<hero-detail
name="ctrl.hero.name"
id="ctrl.hero.id"
save="ctrl.save(ctrl.hero)"
></hero-detail>
And next in component, on $onInit hook, make hero object from primitives:
HeroDetailController.prototype.$onInit = function(){
this.hero = {
name: this.name,
id: this.id
}
console.log('OnInit called');
}
And call specified function when user clicks save. Weird part is, that if user changes hero's name inside component and clicks save, when function bound from parent is called, it does not have changes from hero-detail component. I made a plunker which shows my problem: Plunk which shows problem with children/parent output binding in Angular 1.5 - if you open Developer Console, click "Set name..." and then click save, you will see console.logs which will show you that from hero-detail it is Spawn2, but in parent context (where should be logic, like talking to $http service), it has still old value Spawn. Am I missing something?
Code from Angular docs looks pretty like my code:
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
I have no clue what's going on. Thank you in advance for helping me to deal with this problem.
P.S. I had some problem with Plunk versions, now everything is OK - in Developer Console in your browser you can see problems with updates
To avoid confusion about the scope of variables (parent or child), prefix injected variables with $.
/* WAS
<hero-detail
name="ctrl.hero.name"
id="ctrl.hero.id"
save="ctrl.save(ctrl.hero)"
></hero-detail>
*/
//SHOULD BE
<hero-detail
name="ctrl.hero.name"
id="ctrl.hero.id"
save="ctrl.save($hero)"
></hero-detail>
Then in your code:
HeroDetailController.prototype.saveHero = function(hero) {
console.log('Hero name from child: ' + hero.name);
this.save({
$hero: hero
});
};
This way you can tell which variables of the expression are from the parent scope and which variables are from the directives scope.

MithrilJS: Routing a component inside top level component

I just started reading about Mithril. Fascinating..
Just one thing that puzzles me after first read.
How can I route one component inside another (top-level component)? I mean, how do I emulate angulars ng-view or embers outlet?
I understand that I can get m.route to attach components to any dom node.
But how can I render say top level component App, which generates m("#view") among other things, and then all other routable components go inside App's #view div? Is this possible? Otherwise I have to repeatedly include header and footer with every route transition to a subcomponent, right? Am I missing something?
Thank you.
Otherwise I have to repeatedly include header and footer with every route transition to a subcomponent, right? Am I missing something?
I don't think you're missing anything. Mithril has as little magic as possible, so it's hard to miss things. Yet it's still somehow more convenient than frameworks with magic.
I simply wrap my views in a template function. I'm a lazy guy, but even I don't mind doing this because it's flexible and not confusing.
http://codepen.io/farzher/pen/vOjjEB
function viewTemplate(content) {
return function() {return [
m('#header', [
'my site',
m('a', {config:m.route, href:'/'}, 'home'),
m('a', {config:m.route, href:'/1'}, 'page 1'),
m('a', {config:m.route, href:'/2'}, 'page 2'),
]),
m('hr'),
m("#view", content),
m('#footer', 'copyright my site'),
]}
}
component1 = {
view: viewTemplate([
m('h1', 'component 1 page')
])
}
component2 = {
view: viewTemplate([
m('h1', 'component 2 page')
])
}
m.route(document.body, '/', {
'/': {view: viewTemplate()},
'/1': component1,
'/2': component2,
})
I ended up going with on of the Leo's suggestions I found googling around.
I can only have "one-layer" wrap and no named outlets with this solution but it works and does the job for now.
At the end of the day, Angular has only one ng-view and people get by somehow.
So this is the outer component.
var Layout = {
controller(subcomp) {
this.own = {
slide: false
};
this.subctrl = new subcomp.controller();
this.subview = subcomp.view;
},
view(ctrl) {
return bubble(ctrl.own, ctrl.subview(ctrl.subctrl));
},
wrap(routes) {
var map = {};
Object.keys(routes).map((r) => {
map[r] = {
controller() {
return new Layout.controller(routes[r]);
},
view: Layout.view
};
});
return map;
}
};
This is the outer view where you insert your component.
function bubble(vm, subview) {
return m("main", [
m("#outlet",[ subview ])
]);
}
And then you route all your subcomponents inside the layout.
m.route.mode = "pathname";
m.route(document.body, "/articles/create", Layout.wrap({
"/articles/create": CreateArticle
}));
Hope this helps someone in the same situation.
I tried several solutions:
With m.component for route handler -
http://jsfiddle.net/0xwq00zm/1/
With internal method of the App component, that wraps the inner component. This is somewhat better, cause I'm able to pass aplication state to other components - http://jsfiddle.net/0xwq00zm/11/
With simple external function, that wraps the inner component with
other elements - http://jsfiddle.net/0xwq00zm/12/
More or less complex - with all of them I have the feeling that the apps redraws itself, and not only the inner component.
Simply select all the elements - Ctrl+A in the result pane in JsFiddle - and then navigate. It's virtual DOM and it shouldn't re-render everything, but with all solutions above - it happens.
(I tried also with context.retain = true; on some parts, but still after a few navigations I get to a point where nothing gets selected.)
========
Hope these variants help someone ... but also - I'll be happy to see solution of the total re-rendering.

Passing object available in the template into the {{render}} helper doesn't seem to work

I have an object defined globally as App.configObj which contains a property data. Inside a view's template I can use {{App.configObj.data}} to display the value and it works fine.
Inside that same template, I use {{render "viewC" model config=App.configObj}} to render a similar view, but the config property on that view remains null on didInsertElement. Other arguments set to primitive values are correctly set at that point.
Since App.configObj is definitely available in that context, shouldn't I be able to pass it into that view?
Here is the jsbin that illustrates the situation: http://emberjs.jsbin.com/misiyaki/12/edit
If you comment out the render call for ViewC, you can see that {{App.configObj.data}} renders just fine in the template.
My goal is to use an object encapsulating several properties to configure the view, so I need to be able to pass that object in. I spent a lot of time searching for similar content online but didn't find anyone trying this.
What am I missing?
Thanks!
I understand your struggle here with not being able to pass in a property in your render code... but in this case it doesn't seem that that is truly necessary.
Here is a fiddle with some changes to show you another way, that is essentially the same thing if i understood your intentions correctly. http://emberjs.jsbin.com/misiyaki/15/edit
The new code for your view:
App.ViewCView = Em.View.extend({
name: 'testName',
config: function () {
return App.configObj;
}.property(),
data: function () {
return this.get('config.data')
}.property('config'),
templateName: 'view-c'
});
Hope this helps!

Categories

Resources