Update: I put a bounty on this question. I am not looking for hacks or workarounds. I am looking for an official way to access the dom in an angular component, and an explanation why the behavior I see ($postLink running to early) seems to be contradictory to the official docs.
The official docs state (here):
$postLink() - Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation
Original question: I have an example of the problem here -> http://plnkr.co/edit/rMm9FOwImFRziNG4o0sg?p=preview
I am using an angular component and I want to modify the dom in the post link function, but it doesn't work, it seems that the function runs too early, before the template is actually ready in the dom after all the angular processing.
In the html page, I have this:
<my-grid grid-id="'foo'"></my-grid>
The component is defined as:
appModule.component('myGrid',{ controller: gridController, bindings: { "gridId": "<", }, templateUrl: 'gridTemplate' });
In the component template I have this:
<table id='{{$ctrl.gridId}}'> ...
(The binding itself works, there is no doubt. Eventually, in the html the id of the table is 'foo' as expected).
In the controller, I have something like this:
function gridController($scope, $compile, $attrs) { console.log ("grid id is: " + this.gridId); // 'foo' this.$postLink = function() { var elem = document.getElementById(this.gridId); // do something with elem, but elem is null } }
What I see when debugging is that when the $postLink function is executed, the table is in the dom but its id attribute is still {{$ctrl.gridId}}
instead of foo
, so document.getElementById()
finds nothing. This seems in contrast to the documentation.
What am I missing? Is there a different way to access the dom in the component?
Update 2: Today I realized the same problem occurs with the regular link function of directives, it is not limited to components. So apparently I misunderstood the meaning of "do direct DOM manipulation" - the link function runs on an element that is detached from the dom, so using the document
object with selectors is useless.
The documentation regarding $postLink()
is correct. It's called after its controller's element and its children have been linked. This doesn't mean that you'll see a directive's result immediately. Maybe it's calling $http
and inserting the result once it arrives. Maybe it's registering a watcher which in turns sets the result, as most of Angular's built-in directives do.
The underlying issue in your case is that you want to perform DOM manipulations after the interpolations have been compiled, or better yet, after their registered watcher had had time to run once.
Unfortunately, there isn't an official way to do this. Nevertheless, there are ways of accomplishing this, but you won't find them listed in the documentation.
Two popular ways of running a function after the interpolations have been compiled are:
using $timeout
without a delay (as it defaults to 0): $timeout(function() { /* Your code goes here */ });
using .ready()
which is provided by Angular's jqLite
In your case, you're much better off using a watcher to run a function once the element with the given ID exists:
var deregistrationFn = $scope.$watch(() => { return document.getElementById(this.gridId); }, (newValue) => { if (newValue !== null) { deregistrationFn(); // Your code goes here } });
Finally, in my opinion, I believe that whenever you need to wait for the interpolations to be compiled, or for certain directives to insert their value, you're not following the Angular's way of building things. In your case, why not create a new component, myGridTable
which requires myGrid
as a parent, and add its appropriate logic there. This way, each component's responsibility is much better defined and it's easier to test things.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With