Can I place ui-view on the body tag? - angularjs

I'm working on an app whose header's styling and UI options will change depending upon the state a user is currently in. For example, if a user clicks the Upload, Edit Profile, Create Playlist or Create Gallery buttons in the main menu, this will cause the following changes to the header:
the header's background to change color
the site logo will have CSS3 animation applied to it to make it slide into a new position
a specific message (depending on what state the user is in) in a word bubble will appear next to the logo
a Cancel button will manifest on the right edge of the header
This means there will be no static element on my site so logic tells me that I should place ui-view in my body tag thus making it look like this:
<body ng-app="app" ui-view>
I have never seen that used before anywhere so I've my doubts. Any ideas?

I'm not sure if this will or will not work technically, but I believe you should avoid it because:
It would require all your templates to include an outermost <body> tag which makes them less reusable
It's generally an unusual thing to do, and that comes with a cost, and I see no particular benefit to this, so IMHO the cost/benefit analysis says stick with what is conventional: use <div>s.
I suspect this may in fact work, but typically the tag with ng-app stays in place for the entire lifecycle of the app. There's a chance you may hit some weird errors if you swap out the tag containing ng-app. Thus many people put ng-app on the very first <html> tag and call it done.
But it's your app and the effort to try both approaches is very low, so just try them out and see (and let us know what you learn). I don't think you need to wait for stackoverflow answer from authority to test 2 slightly different HTML structures with your app.

No, please do not do that!
Having ui-view inside of the body tag may render many JS plugins/libraries unusable, especially in case if they dynamically add some HTML tags to document.body. In such cases, navigation to another view will reinitialize document.body and thus disregard previously added HTML tags.
E.g. this is the case with Firebase and Angular.js (AngularFire). I hope everybody who has come across the error
Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
will get to see this post. Just make sure you haven't added ui-view attribute to your body tag.

Related

Is it possible to make GTM to tag some elements that are dynamically created

I'm a React developer and I barely knows how GTM works as it's not my field.
What I understand is
There are triggers on google tag manager
GTM script will find HTML elements of the triggers by defined css selectors(in my case) and tag them(put GTM attributes) when it's loaded in the page
When an event meeting the condition of a trigger occurs, the trigger will execute defined javascript(in my case)
The problem is that HTML elements possibly don't exist when GTM script is tagging because they can be created by a user interaction. And I want to tag such elements by using GTM's lifecycle method(if exists). Is there any way to do this or at least a workaround?
The best practice I can come up with is something like :
When you render the element you want to track in the React.
Let React push the datalayer about the information of the element like
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "element_render",
element_id: "#id",
element_type: "type",
any_paramter_you_need: "any_value_you_need",
})
So you can set up the Trigger in you Google Tag Manager and the type is Custom Event
Usually that should work out of the box. GTM does not tag elements directly. Instead, GTM is attached to the document root. If an element is clicked, the event bubbles up to the document root, and there GTM checks if the properties of the event target (the actually element interacted with) meet the conditions defined in the trigger.
So unless React is doing something super crazy with the document, you should be safe. Allowing to track dynamically added elements is pretty much the point of this design.
For visibility triggers, you specifically need to enable this behavior by clicking the checkbox on "observe DOM changes". There is a warning that this might impact page speed.

Cordova - Angular - <select> tag open a new view

I have an Angular/Cordova app and I'm trying to figure out how should I handle the HTML SELECT tag. What I would like to do is to open a new window with all the options in a list, the user picks one and returns with that value.
The problem is when I do that I lose all the data I had in the first screen as I am closing it when I move to the second one.
I am using Angular's UI.ROUTER. One thing, which I am not too convinced to do, is to save all data entered into StateParams, and when I return, place it back.
What would be the best approach?
It really depends on the use case. If you need to be able to "deep link" to the view where a link loads the view with the pop-up active then using ui-router and stateparams makes the most sense. If deep linking isn't a concern and the user must always select something then you can just use a service/factory/value/provider in order to share the data between the controllers during the lifetime of the app.

$compile is only properly inserting my template the first time that I run it

http://embed.plnkr.co/SPGNLd0bcmo2Xt2TAZcB/preview
Here we have a list of personnel information cards. If you click on one, the directive triggers a template to be loaded between that row and the row before it. My problem is that it only works once!
I believe that my problem lies somewhere in my compile statement, but I'm not sure:
$compile(controller.former)(scope);
What honestly baffles me is that even if you just click on the same card over and over again, ignoring all the others, it still just loads the one time. After the first successful load, the Template insertion is coming up empty. that is, isntead of the full template being inserted, i'm just getting:
<!-- ngInclude: 'focus.html' -->
And not the actual template located in that file. Does anybody have any Idea what is going on here?
I apologize in advance for the relatively complex directive, if anybody has any suggestions for refactoring it, I am an open book.
I think the problem I described is in async manner of getting the template from the server side. In case of cached content it didn't work. You used element bind to click which is anti-angular way and non-angular context (thus you needed yo call $apply). And in this case I guess the problem that $scope.apply works before the content of the compiled node itself is processed (because you get the template immediately from the cache). If you call $scope.apply from the $timeout function or (better) change the function to scope function linked with templates via ng-click it works as expected.
I think for the goal are are going to achieve you are taken a bit wrong design which looks a bit weird. Anyway I can show you the problem - you are using separate template which is compiled inside of the ng-include. ng-include directive actually performs $http request with using template cache (default behavior) because of this the data is loaded only once and the template isn't inserted. If you add random seed to the template URL or make $templateCache.remove('focus.html') before compiling that can solve your issue (but I don't think it's good solution as it makes http call each time). You should definitely re-factor your code to make it more clean.
I changed
controller.former = angular.element('<div ng-include="\'focus.html\'"></div>');
to:
controller.former = angular.element('<div><div ng-include="\'focus.html\'"></div></div>');
By wrapping the template into a parent container, I ensure that controller.former is always a reference to a container that holds all of the elements generated during compile time. without this fix, sometimes controller.former would only be selecting the comment node that precedes the inserted material. This resolves causes problems when i'm passing that selector into $animate.enter or $animate.leave, because it ends up only trying to animate that single comment node and ignoring it's next sibling.

AngularJS jquery.flot chart directive DOM collision

i'm working with angular js now for about ke 4 months and despite all the "first step failures" like not emphasising the async way anuglar thinks, I'm facing a problem I don't really understand. It's not that easy to describe.
I have a provider which registrates directives within the routeprovider's resolve function - during config phase. To compile programmatically preconfigured directives I create them on the specific controller call of each route. Acutally the directive I'm adressing here is a complex flotchart directive. It retrieves data from a rest api, transformes the retrieved data and prepares different kinds of option setups like proper stacked line charts or simple piecharts. Every single step takes its time, so I introduced promises to be sure that everthing is at it's right place before I finally call something like "$.plot".
So now I have the following situation: Imagine a singlepage app with two tabs. Each tab - like a first class menu item - refers to a new page with a new controller to process and new partials to render. For each page i have beside other directives one of these heavy flotchart directives to render. actually it takes about 5 seconds to render the chart. So we assume that we really start the app from beginning - like pressing F5. Now I enter the first page the first time and within the mentioned 5 seconds I switch tabs to enter the next page. I get to the next page, see different partials, layouts and stuff and a loading chart - but actually the directive of the first page is still bound to its link phase of that heavy flotchart directive (still preparing options for flot and calculating data to output graphically).
My problem is that this link phase actually really ends within a completely different template/route/controller context and gets stuck. It crashes with a console "replace" error from jquery.flot. I think this error means that flot tries to plot into a div which does not exist anymore. But that error occures just when I switch tabs during the link phase of the first page's heavy flot directive. It doesn't happen when the first page's chart is fully rendered and doesn't happen when the first page's directive hasn't entered its link phase (or am i missing something??). I tried placing some console.logs directly BEFORE hitting the jquery "$.plot" - remember only of the first page chart directive to dive into what's acutally happening. And the strange thing is when I manage to switch tabs within these magic 5 seconds, I still get the console log entries from the first page entry although I'm on a different page. And now guess what. That's strange - acutally two directive link phases are running side by side and one of them on a completely different view (or isn't it completely different, because its a singel page app?). Imagine I plot ("render") the chart in exactly the same div id - like $('#flot-chart'). so I have html parts containing id="flot-chart" on the first page AND on the second. when I now switch from the first to the second page (not finishing the first chart) I get the chart from the first page rendered in the #flot-chart div of the second page and like half a second later the actually correct chart rendered in that same div. So actually the link phase of the first page's chart directive ends in a completely different page in a way showing 2 charts consecutively. I know jquery.flot depends on DOM manipulation via jquery and that might be the problem (perhaps THAT'S the only real explanation for my problem), because jquery DOM manipulation is out from the angular way of life.
Or are there other explanations? I acutally solved the problem via $routeChangeStart listening and killing the $.plot process, but are there some hints, suggestions, explanations for that behaviour?
Plunker flot chart directive DOM collision
I have prepared a plunker which shows kind of a similar behaviour. i've delayed the creation of the directive and the directive's async data and option retrieval methods to somehow mock the behaviour of my app. this is non production code but describes simplified the way my problems occure. when you "fast click" the menu item one after the other many times, you can sometimes force angular to show 2 charts in one page. acutally one directive is linking and doing stuff in a different partial ? i know i'm missing something in my mind ... please give me a hint.
i used chrome for reproducing the error. stop the the plunker and press 'run'. directly after pressing 'run' click as fast as you can both links a couple of times.
Thanks a lot!

possible to disable $locationProvider and $routeProvider?

Can I disable these fellas? I am using angular.js in asp.net mvc app, and I don't need angular to control anything related to address bar or the links...
Right now in html5 mode disabled ($locationProvider.html5Mode(false)) it adds hash and action method's name to the address-bar, for example: you go to \Home\index, it navigates and then address bar text changes into Home\index#\index. ain't that's annoying?
if I enable html5 mode it stops loading pages at all (except the initial). I try going from initialy loaded page to another - it changes the address-bar's text (without adding hashtag thing this time) but won't load the page itself. ain't that frustrating?
A makeshift solution exists here AngularJS 1.1.5 - automatically adding hash tag to URLs
The answer explains the first step (as explained above, with the addition of the new hash-prefix)
yourApp.config(['$locationProvider', function($locationProvider){
$locationProvider.html5Mode(true).hashPrefix('!');
}]);
The first bit handles Angular interfering with your address-bar visually, but now clicking on any links doesn't function properly as (read: history.pushState)
So, the workaround, as pointed out by user #Kevin Beal is some variation of setting the target of the <a> to _self
$('a[href]').attr({'target':'_self'})
or on a case-by-case basis:
Foo
Bar
Although, for the sake of convenience and sanity, I think it's combination of these.
Markup sans target
Foo
Bar
JS
// If `http` protocol present, assume external link
$('a[href^="http://"]').attr({'target':'_blank'});
// Otherwise, assume internal link
$('a:not([href^="http://"])').attr({'target':'_self'});
Worth noting that the above selectors do required jQuery proper.

Resources