I've run into issues before trying to concatenate a large string and EXEC that using sp_executesql. It's definitely a solution for some people, but at least one time I tried to use it my job started failing repeatedly. Using the cursor worked well to get around that problem, even if it is a row-by-row solution.
As for the WHILE loop, that's really just taking out the CURSOR part and replacing it with something that's not quite a cursor but still goes row-by-row. I don't have a problem using WHILE loops, but to say things like "cursors are bad - use a while loop instead" never struck me as a good solution. Cursors can work well when used properly. In this case, this should hopefully be a one-time solution. I wouldn't tend to advocate this as a nightly job to run for permissions. You should switch over to DB source control of some sort if you're trying to manage permissions better.
Another advantage of the cursor is I can easily just see to which objects I'm about to grant permissions by just running the select portion of the cursor. The dynamic SQL and string concatenation tends to muddy that a little bit.
Use what works for you. I wanted to get the general idea of how you might do this so people can experiment. In my practice, we tend to mostly grant permissions at the Schema level with more granular permissions given as needed. Of course, most of our systems run through service accounts so we don't have a need for more granular control in most cases. If we needed more granular permissions, I'd likely generate them all in some script format, then store those in our SQL Projects so they could be re-used. I'd still grant to a role and then manage role membership through post-deploy scripts. That would be a little more work to set up initially, but I'd not have to touch those for future releases. 🙂
(I should note that I also miss the easy way we used to be able to do this in 6.5 and prior - open permissions, select entire column, grant, save/close, done 🙂 )